diff --git a/.github/dependabot.yml b/.github/dependabot.yml index 1b78160209..165dad4e01 100644 --- a/.github/dependabot.yml +++ b/.github/dependabot.yml @@ -21,14 +21,13 @@ updates: - dependency-name: "@tiptap/extension-code" - dependency-name: "@tiptap/extension-horizontal-rule" - dependency-name: "@tiptap/extension-italic" - - dependency-name: "@tiptap/extension-link" + - dependency-name: "@tiptap/extension-paragraph" - dependency-name: "@tiptap/extension-strike" - dependency-name: "@tiptap/extension-text" - dependency-name: "@tiptap/extension-underline" # prosemirror packages - dependency-name: "prosemirror-changeset" - - dependency-name: "prosemirror-dropcursor" - dependency-name: "prosemirror-highlight" - dependency-name: "prosemirror-model" - dependency-name: "prosemirror-state" @@ -41,6 +40,12 @@ updates: # yjs packages - dependency-name: "yjs" - dependency-name: "y-prosemirror" + ignore: + # Hono packages are used only in the demo AI server and are not part of + # the main editor/runtime surface area. + - dependency-name: "hono" + - dependency-name: "@hono/node-server" + - dependency-name: "@hono/*" groups: editor-dependencies: patterns: diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index aa013cc9a2..91b5ca0414 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -7,8 +7,10 @@ on: types: [opened, synchronize, reopened, edited] env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} + pnpm_config_store_dir: ./node_modules/.pnpm-store jobs: build: @@ -16,23 +18,23 @@ jobs: runs-on: ubuntu-latest timeout-minutes: 60 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 100 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} @@ -41,8 +43,6 @@ jobs: nx-${{ env.NX_BRANCH }}- nx- - # This is needed for the canvas dep, Tiptap V3 should remove the need for this - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - name: Install Dependencies run: pnpm install @@ -55,6 +55,9 @@ jobs: - name: Run unit tests run: pnpm run test + - name: Run Next.js integration test (production build) + run: NEXTJS_TEST_MODE=build npx vitest run tests/src/unit/nextjs/serverUtil.test.ts + - name: Upload webpack stats artifact (editor) uses: relative-ci/agent-upload-artifact-action@v2 with: @@ -65,57 +68,140 @@ jobs: id: soft-release run: pnpx pkg-pr-new publish './packages/*' # TODO disabled only for AI branch--compact - playwright: - name: "Playwright Tests - ${{ matrix.browser }}" + playwright-build: + name: "Playwright Build" runs-on: ubuntu-latest - timeout-minutes: 60 - container: - image: mcr.microsoft.com/playwright:v1.51.1-noble - strategy: - fail-fast: false - matrix: - browser: [chromium, firefox, webkit] + timeout-minutes: 30 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: fetch-depth: 100 - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache - key: nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} + key: nx-playwright-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} restore-keys: | - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- - nx-${{ matrix.browser }}-${{ env.NX_BRANCH }}- + nx-playwright-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}- + nx-playwright-${{ env.NX_BRANCH }}- nx- - - run: apt-get update && apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - - name: Install dependencies run: pnpm install - name: Build packages run: pnpm run build + - name: Upload build artifacts + uses: actions/upload-artifact@v7 + with: + name: playwright-build + path: | + packages/*/dist + playground/dist + retention-days: 1 + + playwright: + name: "Playwright Tests - ${{ matrix.browser }} (${{ matrix.shardIndex }}/${{ matrix.shardTotal }})" + runs-on: ubuntu-latest + needs: playwright-build + timeout-minutes: 30 + container: + image: mcr.microsoft.com/playwright:v1.51.1-noble + strategy: + fail-fast: false + matrix: + browser: [chromium, firefox, webkit] + shardIndex: [1, 2] + shardTotal: [2] + steps: + - uses: actions/checkout@v6 + with: + fetch-depth: 100 + + - name: Install pnpm + uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: "**/pnpm-lock.yaml" + node-version-file: ".nvmrc" + + - name: Download build artifacts + uses: actions/download-artifact@v8 + with: + name: playwright-build + + - name: Install dependencies + run: pnpm install + - name: Run server and Playwright tests run: | - HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }}" pnpm run e2e + HOME=/root PLAYWRIGHT_CONFIG="--project ${{ matrix.browser }} --shard=${{ matrix.shardIndex }}/${{ matrix.shardTotal }}" pnpm run e2e + + - name: Upload blob report + uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: blob-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/blob-report/ + retention-days: 1 + + - name: Upload HTML report + uses: actions/upload-artifact@v7 + if: ${{ !cancelled() }} + with: + name: playwright-report-${{ matrix.browser }}-${{ matrix.shardIndex }} + path: tests/playwright-report/ + retention-days: 30 + + merge-reports: + name: "Merge Playwright Reports" + if: ${{ !cancelled() }} + needs: playwright + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v6 + + - name: Install pnpm + uses: pnpm/action-setup@v5 + + - uses: actions/setup-node@v6 + with: + cache: "pnpm" + cache-dependency-path: "**/pnpm-lock.yaml" + node-version-file: ".nvmrc" + + - name: Install dependencies + run: pnpm install + + - name: Download blob reports + uses: actions/download-artifact@v8 + with: + path: tests/all-blob-reports + pattern: blob-report-* + merge-multiple: true + + - name: Merge reports + run: npx playwright merge-reports --reporter html ./all-blob-reports + working-directory: tests - - uses: actions/upload-artifact@v4 - if: always() + - name: Upload merged HTML report + uses: actions/upload-artifact@v7 with: - name: playwright-report-${{ matrix.browser }} + name: playwright-report-merged path: tests/playwright-report/ retention-days: 30 diff --git a/.github/workflows/fresh-install-tests.yml b/.github/workflows/fresh-install-tests.yml new file mode 100644 index 0000000000..6d6ed4a452 --- /dev/null +++ b/.github/workflows/fresh-install-tests.yml @@ -0,0 +1,144 @@ +name: Fresh Install Tests + +# Periodically tests BlockNote with the latest versions of its production +# dependencies (within declared semver ranges). This catches breakage when a +# new release of a dep like @tiptap/* or prosemirror-* ships and conflicts +# with BlockNote's declared ranges — the kind of failure a user would hit when +# running `npm install @blocknote/react` in a fresh project. +# +# Only production dependencies of published (non-private) packages are updated. +# DevDependencies (vitest, vite, typescript, etc.) stay pinned to the lockfile, +# so test tooling churn doesn't cause false positives. + +on: + schedule: + - cron: "0 2 * * *" # Daily at 02:00 UTC + workflow_dispatch: # Allow manual runs + +env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true + pnpm_config_store_dir: ./node_modules/.pnpm-store + +jobs: + fresh-install-unit-tests: + name: Unit Tests (Fresh Dep Resolution) + runs-on: ubuntu-latest + timeout-minutes: 30 + + steps: + - id: checkout + uses: actions/checkout@v6 + + - id: install_pnpm + name: Install pnpm + uses: pnpm/action-setup@v5 + + - id: setup_node + uses: actions/setup-node@v6 + with: + node-version-file: ".nvmrc" + # Intentionally no pnpm cache — we want fresh prod dep resolution + + - id: install_dependencies + name: Install dependencies + run: pnpm install + + - id: update_prod_deps + name: Update prod deps of published packages + # Resolves production dependencies of every published (non-private) + # workspace package to the latest version within their declared semver + # ranges. This simulates what a user gets when running + # `npm install @blocknote/react` in a fresh project. + # DevDependencies are left at their lockfile versions. + run: | + FILTERS=$(node -e " + const fs = require('fs'); + const path = require('path'); + fs.readdirSync('packages').forEach(dir => { + try { + const pkg = JSON.parse(fs.readFileSync(path.join('packages', dir, 'package.json'), 'utf8')); + if (!pkg.private && pkg.name) process.stdout.write('--filter ' + pkg.name + ' '); + } catch {} + }); + ") + echo "Updating prod deps for: $FILTERS" + eval pnpm update --prod $FILTERS + + - id: dedupe_deps + name: Dedupe transitive dependencies + # After bumping the publishable packages' prod deps, collapse any + # duplicate transitive resolutions (e.g. @tiptap/core + @tiptap/pm) + # that would otherwise differ between the updated publishable packages + # and the un-updated examples/playground. Without this, TypeScript + # treats the two copies' exports as unrelated types and example-editor + # fails to build (TS2322 on Extension vs AnyExtension). + # Dedupe only rewrites the lockfile — it does NOT modify package.json, + # so the examples' "@blocknote/*": "latest" specs (which is what + # CodeSandbox users see) stay intact. + run: pnpm dedupe + + - id: build_packages + name: Build packages + run: pnpm run build + env: + NX_SKIP_NX_CACHE: "true" + + - id: run_unit_tests + name: Run unit tests + run: pnpm run test + env: + NX_SKIP_NX_CACHE: "true" + + - name: Notify Slack on workflow failure + if: ${{ failure() }} + env: + SLACK_WEBHOOK_URL: ${{ secrets.SLACK_WEBHOOK_URL }} + REPOSITORY: ${{ github.repository }} + WORKFLOW: ${{ github.workflow }} + RUN_ID: ${{ github.run_id }} + RUN_NUMBER: ${{ github.run_number }} + RUN_ATTEMPT: ${{ github.run_attempt }} + BRANCH: ${{ github.ref_name }} + run: | + if [ -z "$SLACK_WEBHOOK_URL" ]; then + echo "SLACK_WEBHOOK_URL is not configured; skipping Slack notification." + exit 0 + fi + + failed_step="Unknown step" + if [ "${{ steps.checkout.outcome }}" = "failure" ]; then + failed_step="Checkout repository" + elif [ "${{ steps.install_pnpm.outcome }}" = "failure" ]; then + failed_step="Install pnpm" + elif [ "${{ steps.setup_node.outcome }}" = "failure" ]; then + failed_step="Setup Node.js" + elif [ "${{ steps.install_dependencies.outcome }}" = "failure" ]; then + failed_step="Install dependencies" + elif [ "${{ steps.update_prod_deps.outcome }}" = "failure" ]; then + failed_step="Update prod deps of published packages" + elif [ "${{ steps.dedupe_deps.outcome }}" = "failure" ]; then + failed_step="Dedupe transitive dependencies" + elif [ "${{ steps.build_packages.outcome }}" = "failure" ]; then + failed_step="Build packages" + elif [ "${{ steps.run_unit_tests.outcome }}" = "failure" ]; then + failed_step="Run unit tests" + fi + + run_url="https://github.com/${REPOSITORY}/actions/runs/${RUN_ID}" + message=$(printf '%s\n%s\n%s\n%s' \ + ":warning: Fresh Install Tests failed in *${REPOSITORY}* on branch *${BRANCH}*." \ + "*Workflow:* ${WORKFLOW}" \ + "*Run:* <${run_url}|#${RUN_NUMBER} (attempt ${RUN_ATTEMPT})>" \ + "*Failed step:* ${failed_step}") + payload=$(jq --compact-output --null-input --arg text "$message" '{text: $text}') + + curl -sS -X POST \ + --fail \ + --retry 4 \ + --retry-all-errors \ + --retry-max-time 60 \ + --connect-timeout 10 \ + --max-time 30 \ + -H "Content-type: application/json" \ + --data "$payload" \ + "$SLACK_WEBHOOK_URL" diff --git a/.github/workflows/publish.yaml b/.github/workflows/publish.yaml index 511fc6d2b2..280d5a5af1 100644 --- a/.github/workflows/publish.yaml +++ b/.github/workflows/publish.yaml @@ -14,8 +14,10 @@ on: type: string env: + FORCE_JAVASCRIPT_ACTIONS_TO_NODE24: true NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_ACCESS_TOKEN }} NX_SELF_HOSTED_REMOTE_CACHE_SERVER: ${{ secrets.NX_SELF_HOSTED_REMOTE_CACHE_SERVER }} + pnpm_config_store_dir: ./node_modules/.pnpm-store jobs: publish: @@ -27,7 +29,7 @@ jobs: attestations: write timeout-minutes: 10 steps: - - uses: actions/checkout@v4 + - uses: actions/checkout@v6 with: ref: ${{ inputs.version }} fetch-depth: 100 @@ -39,20 +41,20 @@ jobs: id: package-manager-version - name: Install pnpm - uses: pnpm/action-setup@v4 + uses: pnpm/action-setup@v5 with: version: ${{ steps.package-manager-version.outputs.stdout }} - - uses: nrwl/nx-set-shas@v3 + - uses: nrwl/nx-set-shas@v5 - - uses: actions/setup-node@v4 + - uses: actions/setup-node@v6 with: cache: "pnpm" cache-dependency-path: "**/pnpm-lock.yaml" node-version-file: ".nvmrc" - name: Cache NX - uses: actions/cache@v4 + uses: actions/cache@v5 with: path: .nx/cache key: nx-${{ env.NX_BRANCH }}-${{ env.NX_RUN_GROUP }}-${{ github.sha }} @@ -61,8 +63,6 @@ jobs: nx-${{ env.NX_BRANCH }}- nx- - - run: sudo apt-get update && sudo apt-get install -y build-essential libcairo2-dev libpango1.0-dev libjpeg-dev libgif-dev librsvg2-dev pkg-config - - name: Install Dependencies & Build run: pnpm install && pnpm build diff --git a/.gitignore b/.gitignore index 92f64ba492..898dcdcc62 100644 --- a/.gitignore +++ b/.gitignore @@ -29,10 +29,15 @@ yarn-error.log* .vercel test-results/ playwright-report/ +blob-report/ release /test-results/ /playwright-report/ +/blob-report/ /playwright/.cache/ .env *.pem .nx/ +# Nightshift plan artifacts (keep out of version control) +.nightshift-plan +.claude \ No newline at end of file diff --git a/CHANGELOG.md b/CHANGELOG.md index 01451c5218..112608f8ff 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,172 @@ +## 0.51.0 (2026-05-14) + +### 🚀 Features + +- Trailing block extension rewrite ([#2733](https://github.com/TypeCellOS/BlockNote/pull/2733)) +- **markdown:** replace unified.js with custom markdown parser/serializer ([#2624](https://github.com/TypeCellOS/BlockNote/pull/2624)) +- **react:** configurable portal targets for floating UI ([#2729](https://github.com/TypeCellOS/BlockNote/pull/2729), [#2692](https://github.com/TypeCellOS/BlockNote/issues/2692)) + +### 🩹 Fixes + +- Pasting plain text from VSCode (BLO-366) ([#2713](https://github.com/TypeCellOS/BlockNote/pull/2713)) +- Parse new lines in `text/plain` as line breaks (BLO-1170) ([#2712](https://github.com/TypeCellOS/BlockNote/pull/2712)) +- Code block PDF export (BLO-987) ([#2725](https://github.com/TypeCellOS/BlockNote/pull/2725)) +- Formatting toolbar opening when inserting file block with `trailingBlock: false` (BLO-860) ([#2704](https://github.com/TypeCellOS/BlockNote/pull/2704)) +- numbered list item decorations missed on initial render ([#2734](https://github.com/TypeCellOS/BlockNote/pull/2734)) +- flicker-free mobile formatting toolbar via CSS custom properties ([#2617](https://github.com/TypeCellOS/BlockNote/pull/2617), [#2616](https://github.com/TypeCellOS/BlockNote/issues/2616)) +- add `bn-thread-orphaned` CSS class to distinguish orphaned threads ([#2737](https://github.com/TypeCellOS/BlockNote/pull/2737), [#2735](https://github.com/TypeCellOS/BlockNote/issues/2735)) +- set width attribute on image and video elements in editor render ([#2740](https://github.com/TypeCellOS/BlockNote/pull/2740), [#2726](https://github.com/TypeCellOS/BlockNote/issues/2726)) +- **a11y:** use figure/figcaption for media block captions ([#2717](https://github.com/TypeCellOS/BlockNote/pull/2717)) +- **ai:** loosen serialization of blocks in columns ([#2716](https://github.com/TypeCellOS/BlockNote/pull/2716), [#2718](https://github.com/TypeCellOS/BlockNote/pull/2718)) +- **core:** trigger codeblock input rule on Enter and place cursor inside ([#2686](https://github.com/TypeCellOS/BlockNote/pull/2686)) +- **core:** preserve list item type when pasting into empty list items ([#2722](https://github.com/TypeCellOS/BlockNote/pull/2722), [#2330](https://github.com/TypeCellOS/BlockNote/issues/2330)) +- **core:** unmount editors in transformPasted tests to prevent unhandled error ([e62880b21](https://github.com/TypeCellOS/BlockNote/commit/e62880b21)) +- **drag-n-drop:** support PDF block drag & drop (BLO-893) ([#2714](https://github.com/TypeCellOS/BlockNote/pull/2714)) +- **i18:** improve french translation for empty toggle list ([#2721](https://github.com/TypeCellOS/BlockNote/pull/2721)) +- **markdown:** emit tight lists when serializing blocks to markdown ([#2715](https://github.com/TypeCellOS/BlockNote/pull/2715)) +- **markdown:** skip placeholder text for empty files ([#434](https://github.com/TypeCellOS/BlockNote/pull/434), [#2719](https://github.com/TypeCellOS/BlockNote/pull/2719)) +- **markdown:** stable round-trip for tables, captions, and audio ([#2720](https://github.com/TypeCellOS/BlockNote/pull/2720)) +- **tests:** stabilize webkit keyboard handler tests with programmatic cursor positioning ([#2746](https://github.com/TypeCellOS/BlockNote/pull/2746)) + +### ❤️ Thank You + +- Cyril G +- Manuel Raynaud @lunika +- Matthew Lipski @matthewlipski +- Movm +- Nick Perez +- Nick the Sick @nperez0111 + +## 0.50.0 (2026-05-04) + +### 🚀 Features + +- Dark mode styling for file block wrapper component (BLO-866) ([#2680](https://github.com/TypeCellOS/BlockNote/pull/2680)) +- Drag hendle menu delete button removes all other blocks in selection (BLO-1007) ([#2683](https://github.com/TypeCellOS/BlockNote/pull/2683)) +- Enter moves selection to cell below in tables (BLO-1006) ([#2685](https://github.com/TypeCellOS/BlockNote/pull/2685)) +- additional heading top padding (BLO-1008) ([#2690](https://github.com/TypeCellOS/BlockNote/pull/2690)) +- Code mark input rule edge case (BLO-938) ([#2698](https://github.com/TypeCellOS/BlockNote/pull/2698)) +- **mantine:** upgrade @mantine/core and @mantine/hooks to v9.0.2 ([#2655](https://github.com/TypeCellOS/BlockNote/pull/2655)) + +### 🩹 Fixes + +- Hardcoded strings in comment components (BLO-1033) ([#2681](https://github.com/TypeCellOS/BlockNote/pull/2681)) +- Color naming & CSS (BLO-946) ([#2684](https://github.com/TypeCellOS/BlockNote/pull/2684)) +- link HTML attributes (BLO-915) ([#2687](https://github.com/TypeCellOS/BlockNote/pull/2687)) +- guard hideMenuIfNotFrozen against undefined view state ([#2694](https://github.com/TypeCellOS/BlockNote/pull/2694), [#2699](https://github.com/TypeCellOS/BlockNote/pull/2699)) +- Clicking comment overlapping link opens link (BLO-1091) ([#2696](https://github.com/TypeCellOS/BlockNote/pull/2696)) +- prevent table row drag from moving an extra adjacent row ([#2703](https://github.com/TypeCellOS/BlockNote/pull/2703)) +- **clipboard:** use ProseMirror selection state for Shadow DOM compatibility ([#2677](https://github.com/TypeCellOS/BlockNote/pull/2677)) + +### ❤️ Thank You + +- jt_fox @LimChaeJune +- Matthew Lipski @matthewlipski +- Nick Perez +- Wieland Lindenthal +- Yousef + +## 0.49.0 (2026-04-24) + +### 🚀 Features + +- simplify links by inlining it to BlockNote ([#2623](https://github.com/TypeCellOS/BlockNote/pull/2623)) +- add Unicode quotation mark input rule for quote blocks ([#2673](https://github.com/TypeCellOS/BlockNote/pull/2673)) + +### 🩹 Fixes + +- Inserting link removes comment & add comment button click buggy ([#2620](https://github.com/TypeCellOS/BlockNote/pull/2620), [#2573](https://github.com/TypeCellOS/BlockNote/issues/2573)) +- `useEditorDOMElement` hook ([#2619](https://github.com/TypeCellOS/BlockNote/pull/2619)) +- text color was not applying to table block ([#2663](https://github.com/TypeCellOS/BlockNote/pull/2663)) +- Drag preview blocking drops when overlapping the editor (BLO-996) ([#2670](https://github.com/TypeCellOS/BlockNote/pull/2670)) +- Drag & drop of blocks without inline content opens formatting toolbar (BLO-1116) ([#2628](https://github.com/TypeCellOS/BlockNote/pull/2628), [#2603](https://github.com/TypeCellOS/BlockNote/issues/2603)) +- save file caption/name on every keystroke instead of on close ([#2575](https://github.com/TypeCellOS/BlockNote/pull/2575)) +- prevent FloatingFocusManager from resetting editor selection ([#2525](https://github.com/TypeCellOS/BlockNote/pull/2525), [#2664](https://github.com/TypeCellOS/BlockNote/pull/2664)) + +### ❤️ Thank You + +- Matthew Lipski @matthewlipski +- miadnguyen @miadnguyen +- mianguyen +- Nick Perez +- Yousef + +## 0.48.1 (2026-04-16) + +### 🩹 Fixes + +- make CustomChange compatible with prosemirror-changeset 2.4.1 ([#2647](https://github.com/TypeCellOS/BlockNote/pull/2647)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability (CVE-2025-62718) ([c1ef3018a](https://github.com/TypeCellOS/BlockNote/commit/c1ef3018a)) +- **deps:** upgrade nx to 22.6.5 to resolve axios security vulnerability ([#2653](https://github.com/TypeCellOS/BlockNote/pull/2653)) +- **docx-exporter:** omit w:lang when no locale provided instead of defaulting to en-US ([#2651](https://github.com/TypeCellOS/BlockNote/pull/2651)) + +### ❤️ Thank You + +- Claude Opus 4.6 (1M context) +- Nick Perez +- Nick the Sick +- Stephan Meijer @StephanMeijer + +## 0.48.0 (2026-04-13) + +### 🚀 Features + +- upgrade shiki to v4 and prosemirror-highlight to v0.15.1 ([#2625](https://github.com/TypeCellOS/BlockNote/pull/2625)) +- upgrade nx to 22.6.4 and liveblocks to 3.17.0 ([#2627](https://github.com/TypeCellOS/BlockNote/pull/2627)) + +### 🩹 Fixes + +- Image block selection clears on mouse leave in Safari ([#2613](https://github.com/TypeCellOS/BlockNote/pull/2613)) +- Backspace bug when current block is empty and previous block's last child is empty ([#2610](https://github.com/TypeCellOS/BlockNote/pull/2610)) +- allow using latest @tiptap/extension-link version ([1ae8de713](https://github.com/TypeCellOS/BlockNote/commit/1ae8de713)) +- restore depth guard in getParentBlockInfo to prevent RangeError (blo-1103) ([#2585](https://github.com/TypeCellOS/BlockNote/pull/2585)) +- pin better-auth to ~1.4.x to fix docs build ([bda30458a](https://github.com/TypeCellOS/BlockNote/commit/bda30458a)) +- hide side menu on scroll instead of overflow hacks ([#2630](https://github.com/TypeCellOS/BlockNote/pull/2630), [#2043](https://github.com/TypeCellOS/BlockNote/issues/2043)) +- disable default UI when no components context is found ([#2611](https://github.com/TypeCellOS/BlockNote/pull/2611)) +- add .js extension to fast-deep-equal ESM import ([#2641](https://github.com/TypeCellOS/BlockNote/pull/2641)) +- placeholder when overflowing now wraps ([#2291](https://github.com/TypeCellOS/BlockNote/pull/2291)) +- **core:** fix unnesting blocks with siblings (BLO-1017) ([#2601](https://github.com/TypeCellOS/BlockNote/pull/2601)) +- **core:** backspace mid-text next to columnList moves block BLO-1126 ([#2629](https://github.com/TypeCellOS/BlockNote/pull/2629)) + +### 🔥 Performance + +- optimize plugin traversals for large documents BLO-1111 ([#2600](https://github.com/TypeCellOS/BlockNote/pull/2600)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Claude Opus 4.6 (1M context) +- hedi-ghodhbane @hedi-ghodhbane +- Matthew Lipski @matthewlipski +- Nick Perez +- Nick the Sick @nperez0111 +- Yousef + +## 0.47.3 (2026-03-25) + +### 🩹 Fixes + +- **core:** preserve whitespace edge cases but collapse html formatting newlines (BLO-1065) ([#2551](https://github.com/TypeCellOS/BlockNote/pull/2551), [#2230](https://github.com/TypeCellOS/BlockNote/issues/2230)) + +### ❤️ Thank You + +- Yousef + +## 0.47.2 (2026-03-20) + +### 🩹 Fixes + +- use
/ for toggle block HTML export ([#2524](https://github.com/TypeCellOS/BlockNote/pull/2524)) +- remove @hocuspocus/provider peer dependency by inlining tiptap comment types BLO-1064 ([#2564](https://github.com/TypeCellOS/BlockNote/pull/2564)) +- **core:** slash menu fails in custom blocks after space BLO-1036 ([#2553](https://github.com/TypeCellOS/BlockNote/pull/2553)) +- **i18n:** fix typo in russian translation ([#2560](https://github.com/TypeCellOS/BlockNote/pull/2560)) + +### ❤️ Thank You + +- Claude Opus 4.6 +- Drone +- Yousef + ## 0.47.1 (2026-03-02) ### 🩹 Fixes diff --git a/docs/app/demo/_components/DemoEditor.tsx b/docs/app/demo/_components/DemoEditor.tsx index 08b290621a..e33b43110b 100644 --- a/docs/app/demo/_components/DemoEditor.tsx +++ b/docs/app/demo/_components/DemoEditor.tsx @@ -119,7 +119,7 @@ export function DemoEditor() { : ""; const content = !roomId ? ( -
+
{}} user={HARDCODED_USERS[0]} @@ -138,15 +138,15 @@ export function DemoEditor() { return (
-
+
⚡️ Collaborate live! Share this URL: -
+
e.currentTarget.select()} /> -
+
-
- - - -
+ {exportOpen && ( +
+ + + +
+ )}
-
+
- ); } diff --git a/docs/app/pricing/PricingTiers.tsx b/docs/app/pricing/PricingTiers.tsx new file mode 100644 index 0000000000..0c7c1c9d48 --- /dev/null +++ b/docs/app/pricing/PricingTiers.tsx @@ -0,0 +1,59 @@ +"use client"; + +import { cn } from "@/lib/fumadocs/cn"; +import { useState } from "react"; +import { Tier, Tiers } from "./tiers"; + +type Frequency = "month" | "year"; + +export function PricingTiers({ tiers }: { tiers: Tier[] }) { + const [frequency, setFrequency] = useState("year"); + + return ( + <> + {/* Frequency Toggle */} +
+ + Monthly + + + + Yearly + + + Save 50% + +
+ + + + ); +} diff --git a/docs/app/pricing/page.tsx b/docs/app/pricing/page.tsx index e7c1368186..ae4e110756 100644 --- a/docs/app/pricing/page.tsx +++ b/docs/app/pricing/page.tsx @@ -1,5 +1,5 @@ import { FAQ } from "@/app/pricing/faq"; -import { Tier, Tiers } from "@/app/pricing/tiers"; +import { Tier } from "@/app/pricing/tiers"; import { InfiniteSlider } from "@/components/InfiniteSlider"; import { Tooltip, @@ -9,6 +9,7 @@ import { } from "@/components/ui/tooltip"; import { getFullMetadata } from "@/lib/getFullMetadata"; import Link from "next/link"; +import { PricingTiers } from "./PricingTiers"; export const metadata = getFullMetadata({ title: "Pricing", @@ -80,7 +81,7 @@ const tiers: Tier[] = [ badge: "Recommended", description: "Commercial license for access to advanced features and technical support.", - price: { month: 390, year: 48 }, + price: { month: 390, year: 2340 }, features: [ Commercial license for XL packages: @@ -162,8 +163,8 @@ export default function Pricing() {

- {/* Pricing Tiers */} - + {/* Pricing Tiers with Toggle */} + {/* Social proof */}
@@ -207,10 +208,6 @@ export default function Pricing() { {/* FAQ */}
-
); } diff --git a/docs/app/pricing/tiers.tsx b/docs/app/pricing/tiers.tsx index 70702d76ec..e9a6013284 100644 --- a/docs/app/pricing/tiers.tsx +++ b/docs/app/pricing/tiers.tsx @@ -23,7 +23,13 @@ export type Tier = { cta?: "get-started" | "buy" | "contact"; }; -function TierCTAButton({ tier }: { tier: Tier }) { +const BUSINESS_PLAN_TYPES = new Set(["business", "business-yearly"]); + +function isBusinessPlan(planType: string) { + return BUSINESS_PLAN_TYPES.has(planType); +} + +function TierCTAButton({ tier, frequency }: { tier: Tier; frequency: Frequency }) { const { data: session } = useSession(); let text = tier.cta === "get-started" @@ -38,10 +44,11 @@ function TierCTAButton({ tier }: { tier: Tier }) { if (session.planType === "free") { text = "Buy now"; } else { - text = - session.planType === tier.id - ? "Manage subscription" - : "Update subscription"; + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + text = isCurrentPlan ? "Manage subscription" : "Update subscription"; } } @@ -68,9 +75,6 @@ function TierCTAButton({ tier }: { tier: Tier }) { } track("Signup", { tier: tier.id }); - // ... rest of analytic logic kept simple for brevity in replacement, - // in real implementation we keep the existing logic. - // Re-injecting existing analytics logic below to ensure no regression. if (!session) { Sentry.captureEvent({ message: "click-pricing-signup", @@ -90,9 +94,16 @@ function TierCTAButton({ tier }: { tier: Tier }) { track("click-pricing-buy-now", { tier: tier.id }); e.preventDefault(); e.stopPropagation(); - await authClient.checkout({ slug: tier.id }); + const checkoutSlug = frequency === "year" && tier.id === "business" + ? "business-yearly" + : tier.id; + await authClient.checkout({ slug: checkoutSlug }); } else { - if (session.planType === tier.id) { + const isCurrentPlan = + tier.id === "business" + ? isBusinessPlan(session.planType ?? "") + : session.planType === tier.id; + if (isCurrentPlan) { Sentry.captureEvent({ message: "click-pricing-manage-subscription", level: "info", @@ -208,6 +219,28 @@ export function Tiers({ {tier.price} + ) : frequency === "year" ? ( +
+
+ + ${Math.round(tier.price.year / 12)} + + + /month + +
+
+ + ${tier.price.month}/mo + + + now -50% + +
+

+ ${tier.price.year.toLocaleString()} billed yearly +

+
) : (
@@ -227,7 +260,7 @@ export function Tiers({ {/* CTA */}
- +
{/* Features */} diff --git a/docs/app/styles.css b/docs/app/styles.css index 7aa8d6bbc7..c1f4c85362 100644 --- a/docs/app/styles.css +++ b/docs/app/styles.css @@ -53,14 +53,6 @@ body { box-shadow: unset !important; } -.demo { - overflow: none; -} - -.demo .bn-container { - position: relative; -} - .demo .bn-container:not(.bn-comment-editor), .demo .bn-editor { height: 100%; diff --git a/docs/content/docs/features/blocks/inline-content.mdx b/docs/content/docs/features/blocks/inline-content.mdx index ca7799d841..a22e93f19c 100644 --- a/docs/content/docs/features/blocks/inline-content.mdx +++ b/docs/content/docs/features/blocks/inline-content.mdx @@ -79,6 +79,55 @@ type Link = { }; ``` +### Customizing Links + +You can customize how links are rendered and how they respond to clicks with the `links` editor option. + +```ts +const editor = BlockNoteEditor.create({ + links: { + HTMLAttributes: { + class: "my-link-class", + target: "_blank", + }, + onClick: (event) => { + // Custom click logic, e.g. routing without a page reload. + }, + }, +}); +``` + +#### `HTMLAttributes` + +Additional HTML attributes that should be added to rendered link elements. + +```ts +const editor = BlockNoteEditor.create({ + links: { + HTMLAttributes: { + class: "my-link-class", + target: "_blank", + }, + }, +}); +``` + +#### `onClick` + +Custom handler invoked when a link is clicked. If left `undefined`, links are opened in a new window on click (the default behavior). If provided, that default behavior is disabled and this function is called instead. + +Returning `false` will let BlockNote run other click handlers after this one. Returning `true` or nothing (the default) marks the event as handled. + +```ts +const editor = BlockNoteEditor.create({ + links: { + onClick: (event) => { + // Do something when a link is clicked. + }, + }, +}); +``` + ## Default Styles The default text formatting options in BlockNote are represented by the `Styles` in the default schema: diff --git a/docs/content/docs/features/collaboration/comments.mdx b/docs/content/docs/features/collaboration/comments.mdx index e03aa26ccf..4b88225993 100644 --- a/docs/content/docs/features/collaboration/comments.mdx +++ b/docs/content/docs/features/collaboration/comments.mdx @@ -96,7 +96,7 @@ import { TiptapThreadStore, DefaultThreadStoreAuth, } from "@blocknote/core/comments"; -import { TiptapCollabProvider } from "@hocuspocus/provider"; +import { TiptapCollabProvider } from "@tiptap-pro/provider"; // Create a TiptapCollabProvider (you probably have this already) const provider = new TiptapCollabProvider({ diff --git a/docs/content/docs/features/import/markdown.mdx b/docs/content/docs/features/import/markdown.mdx index e95d71dabd..3106fd8541 100644 --- a/docs/content/docs/features/import/markdown.mdx +++ b/docs/content/docs/features/import/markdown.mdx @@ -15,6 +15,12 @@ imageTitle: Markdown Import BlockNote can import Markdown content into Block objects. Note that this is considered "lossy", as not all Markdown structures can be entirely represented as BlockNote blocks. + + **BlockNote ships a minimal Markdown parser.** It covers the common subset used by most users (CommonMark + GFM basics: headings, paragraphs, lists, task lists, tables, code, blockquotes, links, images, emphasis, strikethrough, hard breaks). + + There are many Markdown specifications (CommonMark, GFM, MDX, Pandoc, and various dialect-specific extensions) and supporting all of them inside a rich text editor is not a goal of BlockNote. **If you need to handle Markdown beyond this minimal subset, parse it to HTML yourself with a parser of your choice (e.g. [`marked`](https://github.com/markedjs/marked), [`markdown-it`](https://github.com/markdown-it/markdown-it), or [`remark`](https://github.com/remarkjs/remark)) and pass the resulting HTML to [`tryParseHTMLToBlocks`](/docs/features/import) instead.** BlockNote's HTML interoperability is much broader, since HTML is the format the editor uses internally for arbitrary pastes. + + ## Markdown to Blocks Use `tryParseMarkdownToBlocks` to try parsing a Markdown string into `Block` objects: diff --git a/docs/content/docs/features/server-processing.mdx b/docs/content/docs/features/server-processing.mdx index 505edadce7..8488e00639 100644 --- a/docs/content/docs/features/server-processing.mdx +++ b/docs/content/docs/features/server-processing.mdx @@ -52,3 +52,21 @@ const html = await editor.withReactContext( async () => editor.blocksToFullHTML(blocks), ); ``` + +## Next.js App Router + +If you're using `@blocknote/server-util` in a Next.js App Router API route (Route Handler), you need to add the BlockNote packages to `serverExternalPackages` in your `next.config.ts`: + +```typescript +import type { NextConfig } from "next"; + +const nextConfig: NextConfig = { + serverExternalPackages: [ + "@blocknote/core", + "@blocknote/react", + "@blocknote/server-util", + ], +}; + +export default nextConfig; +``` diff --git a/docs/content/docs/foundations/supported-formats.mdx b/docs/content/docs/foundations/supported-formats.mdx index 4b5c89e71c..c5e302e19a 100644 --- a/docs/content/docs/foundations/supported-formats.mdx +++ b/docs/content/docs/foundations/supported-formats.mdx @@ -165,6 +165,20 @@ export default function App() { BlockNote also supports converting to and from Markdown. However, converting to and from Markdown is a **lossy** conversion. + + BlockNote ships a **minimal** Markdown parser/serializer that targets the + common CommonMark + GFM subset (headings, paragraphs, lists, task lists, + tables, code, blockquotes, links, images, emphasis, strikethrough, hard + breaks). Supporting every Markdown dialect (CommonMark, GFM, MDX, Pandoc, + and various extensions) is not a goal for the editor. If your use case + requires Markdown features beyond this subset, **parse the Markdown to + HTML yourself** (with a library like [`marked`](https://github.com/markedjs/marked), + [`markdown-it`](https://github.com/markdown-it/markdown-it), or + [`remark`](https://github.com/remarkjs/remark)) and feed the resulting + HTML to `editor.tryParseHTMLToBlocks` — HTML is the format BlockNote uses + for arbitrary pastes and has much broader interoperability. + + ### Saving as Markdown To convert the document to a Markdown string, you can use `editor.blocksToMarkdownLossy()`: diff --git a/docs/content/docs/getting-started/nextjs.mdx b/docs/content/docs/getting-started/nextjs.mdx index 8e3a73a8ce..a521ca10a0 100644 --- a/docs/content/docs/getting-started/nextjs.mdx +++ b/docs/content/docs/getting-started/nextjs.mdx @@ -58,14 +58,4 @@ function App() { } ``` -## React 19 / Next 15 StrictMode - -BlockNote is not yet compatible with React 19 / Next 15 StrictMode. For now, disable StrictMode in your `next.config.ts`: - -```typescript -... -reactStrictMode: false, -... -``` - This should resolve any issues you might run into when embedding BlockNote in your Next.js React app! diff --git a/docs/content/docs/react/components/index.mdx b/docs/content/docs/react/components/index.mdx index 72deabe9ab..1b6e18d3c0 100644 --- a/docs/content/docs/react/components/index.mdx +++ b/docs/content/docs/react/components/index.mdx @@ -14,3 +14,23 @@ BlockNote includes a number of UI Components (like menus and toolbars) that can {/* - [Image Toolbar](/docs/react/components/image-toolbar) */} + +## Configuring Portal Targets + +By default, all floating UI elements (toolbars, menus, table handles, etc.) portal into the editor's `bn-container` so they stay scoped to the editor. If your layout needs them to escape — e.g. an `overflow: hidden` ancestor that would clip large dropdowns, or a host modal with its own stacking context — pass a `portalElements` prop to `BlockNoteView`: + +```tsx + +``` + +Keys mirror the default UI flags (`formattingToolbar`, `linkToolbar`, `slashMenu`, `emojiPicker`, `sideMenu`, `filePanel`, `tableHandles`, `comments`). Manually-mounted Controllers also accept a `portalElement` prop that takes precedence over the map. See the [Portal Targets example](/examples/ui-components/portal-elements). + +Note: changing `portalElements.default` after mount requires remounting the editor (`editor.mount()` consults it once); per-element keys update reactively. diff --git a/docs/content/docs/react/components/suggestion-menus.mdx b/docs/content/docs/react/components/suggestion-menus.mdx index db9b8421a6..c44f18af45 100644 --- a/docs/content/docs/react/components/suggestion-menus.mdx +++ b/docs/content/docs/react/components/suggestion-menus.mdx @@ -58,6 +58,70 @@ Passing `slashMenu={false}` to `BlockNoteView` tells BlockNote not to show the d `getItems` should return the items that need to be shown in the Slash Menu, based on a `query` entered by the user (anything the user types after the `triggerCharacter`). In this case, we simply append the "Hello World" item to the default Slash Menu items, and use `filterSuggestionItems` to filter the full list of items based on the user query. +### Item Grouping & Ordering + +Slash Menu items are rendered in the same order as the items returned from `getItems`. Adjacent items which share the same `group` attribute are rendered together in the same group under a single label. + +#### Ordering + +Items appear in the menu in the exact order of the array. Reordering the array reorders the menu: + +```typescript +getItems={async (query) => + filterSuggestionItems( + [ + insertHelloWorldItem(editor), // Shown first + ...getDefaultReactSlashMenuItems(editor), // Shown after + ], + query, + ) +} +``` + +#### Grouping + +Items with the same `group` attribute must be **adjacent** in the array to be rendered as one group. If items with the same `group` are separated by items with a different `group`, they will be rendered as two separate groups, each with their own label: + +```typescript +// Renders as a single "Basic" group: +[ + { title: "Item A", group: "Basic", /* ... */ }, + { title: "Item B", group: "Basic", /* ... */ }, + { title: "Item C", group: "Other", /* ... */ }, +] + +// Renders as two separate "Basic" groups, with "Other" between them: +[ + { title: "Item A", group: "Basic", /* ... */ }, + { title: "Item C", group: "Other", /* ... */ }, + { title: "Item B", group: "Basic", /* ... */ }, +] +``` + +#### Finding, Inserting, Removing & Reordering Items + +Use regular array operations to manipulate items. For example, to insert a custom item directly after the default `Heading 1` item: + +```typescript +const items = getDefaultReactSlashMenuItems(editor); +const headingIndex = items.findIndex((item) => item.title === "Heading 1"); +items.splice(headingIndex + 1, 0, insertHelloWorldItem(editor)); +``` + +To remove an item: + +```typescript +const items = getDefaultReactSlashMenuItems(editor).filter( + (item) => item.title !== "Heading 1", +); +``` + +To reorder items, sort or rearrange the array however you'd like before returning it from `getItems`. + +The demo below combines these techniques to render only the "Basic blocks" and "Headings" groups, with their order swapped: + + + ### Replacing the Slash Menu Component You can replace the React component used for the Slash Menu with your own, as you can see in the demo below. diff --git a/docs/content/docs/react/overview.mdx b/docs/content/docs/react/overview.mdx index da09fe2a3c..b85d29ab21 100644 --- a/docs/content/docs/react/overview.mdx +++ b/docs/content/docs/react/overview.mdx @@ -45,8 +45,8 @@ The `` component is used to render the editor. It also provides a ### Props - diff --git a/docs/content/docs/react/styling-theming/overriding-css.mdx b/docs/content/docs/react/styling-theming/overriding-css.mdx index fe7c2047c1..bece286f4d 100644 --- a/docs/content/docs/react/styling-theming/overriding-css.mdx +++ b/docs/content/docs/react/styling-theming/overriding-css.mdx @@ -21,8 +21,9 @@ BlockNote uses classes with the `bn-` prefix to style editor elements. Here are #### Editor Structure -- `.bn-container`: Container for editor and all menus/toolbars. -- `.bn-editor`: Main editor element. +- `.bn-root`: Container class both the floating menus / toolbars and the editor +- `.bn-container`: Container around `.bn-editor` +- `.bn-editor`: Main editor element (the "contenteditable"). - `.bn-block`: Individual block element (including nested). - `.bn-block-group`: Container for nested blocks. - `.bn-block-content`: Block content wrapper. diff --git a/docs/content/docs/react/styling-theming/themes.mdx b/docs/content/docs/react/styling-theming/themes.mdx index a0b0631e3e..5965818ca0 100644 --- a/docs/content/docs/react/styling-theming/themes.mdx +++ b/docs/content/docs/react/styling-theming/themes.mdx @@ -67,7 +67,7 @@ Here are each of the theme CSS variables you can set, with values from the defau --bn-border-radius: 6px; ``` -Setting these variables on the `.bn-container[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-container[data-color-scheme="light"]` and `.bn-container[data-color-scheme="dark"]` selectors. +Setting these variables on the `.bn-root[data-color-scheme]` selector will overwrite them for both default light & dark themes. To overwrite variables separately for light & dark themes, use the `.bn-root[data-color-scheme="light"]` and `.bn-root[data-color-scheme="dark"]` selectors. ## Programmatic Configuration diff --git a/docs/content/docs/reference/editor/manipulating-content.mdx b/docs/content/docs/reference/editor/manipulating-content.mdx index 6c2df88dc7..1a9c97c222 100644 --- a/docs/content/docs/reference/editor/manipulating-content.mdx +++ b/docs/content/docs/reference/editor/manipulating-content.mdx @@ -252,11 +252,13 @@ editor.replaceBlocks( #### Reordering Blocks ```typescript -moveBlocksUp(): void -moveBlocksDown(): void +moveBlocksUp(blockIdentifier?: BlockIdentifier): void +moveBlocksDown(blockIdentifier?: BlockIdentifier): void ``` -Moves the currently selected blocks up or down in the document. +Moves the currently selected blocks up or down in the document. If a +`blockIdentifier` is provided, that block is moved instead of the selection, +and the selection is left unchanged. ```typescript // Move selected blocks up @@ -264,6 +266,12 @@ editor.moveBlocksUp(); // Move selected blocks down editor.moveBlocksDown(); + +// Move a specific block up, without changing the selection +editor.moveBlocksUp("block-123"); + +// Move a specific block down, without changing the selection +editor.moveBlocksDown("block-123"); ``` ### Nesting Blocks diff --git a/docs/content/docs/reference/editor/overview.mdx b/docs/content/docs/reference/editor/overview.mdx index 5230aeff02..086d91c754 100644 --- a/docs/content/docs/reference/editor/overview.mdx +++ b/docs/content/docs/reference/editor/overview.mdx @@ -113,8 +113,8 @@ editor.pasteMarkdown("# Hello\n\nThis is **bold** text."); The editor can be configured with the following options when using `BlockNoteEditor.create`: - diff --git a/docs/lib/auth.ts b/docs/lib/auth.ts index cb5a519044..654ab8e9e9 100644 --- a/docs/lib/auth.ts +++ b/docs/lib/auth.ts @@ -207,6 +207,10 @@ export const auth = betterAuth({ productId: PRODUCTS.business.id, // ID of Product from Polar Dashboard slug: PRODUCTS.business.slug, // Custom slug for easy reference in Checkout URL, e.g. /checkout/pro }, + { + productId: PRODUCTS["business-yearly"].id, + slug: PRODUCTS["business-yearly"].slug, + }, { productId: PRODUCTS.starter.id, slug: PRODUCTS.starter.slug, diff --git a/docs/lib/product-list.ts b/docs/lib/product-list.ts index b0e29a553c..49e52033c7 100644 --- a/docs/lib/product-list.ts +++ b/docs/lib/product-list.ts @@ -7,6 +7,14 @@ export const PRODUCTS = { name: "Business", slug: "business", } as const, + "business-yearly": { + id: + process.env.NODE_ENV === "production" + ? "ba3965dc-e1ca-494e-b36a-62e2e41615d4" + : "NOT-CREATED", + name: "Business Yearly", + slug: "business-yearly", + } as const, starter: { id: process.env.NODE_ENV === "production" diff --git a/docs/package.json b/docs/package.json index d29fcc0ff4..3e64e6b44b 100644 --- a/docs/package.json +++ b/docs/package.json @@ -34,14 +34,13 @@ "@blocknote/xl-odt-exporter": "workspace:*", "@blocknote/xl-pdf-exporter": "workspace:*", "@fumadocs/base-ui": "16.5.0", - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@liveblocks/client": "^3.17.0", + "@liveblocks/react": "^3.17.0", + "@liveblocks/react-blocknote": "^3.17.0", + "@liveblocks/react-tiptap": "^3.17.0", + "@liveblocks/react-ui": "^3.17.0", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "@marsidev/react-turnstile": "^1.4.2", "@mui/icons-material": "^5.16.1", "@mui/material": "^5.16.1", @@ -52,11 +51,11 @@ "@react-email/render": "^2.0.4", "@react-pdf/renderer": "^4.3.0", "@sentry/nextjs": "^10.34.0", - "@shikijs/core": "^3.19.0", - "@shikijs/engine-javascript": "^3.19.0", - "@shikijs/langs-precompiled": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0", + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4", "@tiptap/core": "^3.13.0", "@uppy/core": "^3.13.1", "@uppy/dashboard": "^3.9.1", @@ -72,7 +71,7 @@ "@vercel/analytics": "^1.6.1", "@y-sweet/react": "^0.6.3", "ai": "^6.0.5", - "better-auth": "^1.4.15", + "better-auth": "~1.4.15", "better-sqlite3": "^12.6.2", "class-variance-authority": "^0.7.1", "framer-motion": "^12.26.2", @@ -83,18 +82,18 @@ "fumadocs-ui": "npm:@fumadocs/base-ui@16.5.0", "lucide-react": "^0.562.0", "motion": "^12.28.1", - "next": "^16.1.6", + "next": "^16.2.6", "next-themes": "^0.4.6", "nodemailer": "^7.0.12", "pg": "^8.17.1", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", "react-email": "^5.2.5", "react-github-btn": "^1.4.0", "react-icons": "^5.5.0", "react-use-measure": "^2.1.7", "scroll-into-view-if-needed": "^3.1.0", - "shiki": "^3.21.0", + "shiki": "^4", "tailwind-merge": "^3.4.0", "y-partykit": "^0.0.25", "yjs": "^13.6.27", @@ -123,10 +122,10 @@ "@types/react-dom": "^19.2.3", "babel-plugin-react-compiler": "^1.0.0", "eslint": "^9.39.2", - "eslint-config-next": "^16.1.6", + "eslint-config-next": "^16.2.6", "next-validate-link": "^1.6.4", "postcss": "^8.5.6", - "serve": "^14.2.5", + "serve": "^14.2.6", "tailwindcss": "^4.1.18", "tw-animate-css": "^1.4.0", "typescript": "^5.9.3" diff --git a/examples/01-basic/01-minimal/package.json b/examples/01-basic/01-minimal/package.json index af406c6b8f..26f63572f5 100644 --- a/examples/01-basic/01-minimal/package.json +++ b/examples/01-basic/01-minimal/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/02-block-objects/package.json b/examples/01-basic/02-block-objects/package.json index 8b3ca5bf72..908df7ca16 100644 --- a/examples/01-basic/02-block-objects/package.json +++ b/examples/01-basic/02-block-objects/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/02-block-objects/src/App.tsx b/examples/01-basic/02-block-objects/src/App.tsx index c50da7ddba..c3d623f2e8 100644 --- a/examples/01-basic/02-block-objects/src/App.tsx +++ b/examples/01-basic/02-block-objects/src/App.tsx @@ -26,9 +26,6 @@ export default function App() { type: "paragraph", content: "This is a paragraph block", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/01-basic/03-multi-column/package.json b/examples/01-basic/03-multi-column/package.json index dbcada18f2..2ce018ce9c 100644 --- a/examples/01-basic/03-multi-column/package.json +++ b/examples/01-basic/03-multi-column/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-multi-column": "latest" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/03-multi-column/src/App.tsx b/examples/01-basic/03-multi-column/src/App.tsx index 3a0d10d8d9..c688406214 100644 --- a/examples/01-basic/03-multi-column/src/App.tsx +++ b/examples/01-basic/03-multi-column/src/App.tsx @@ -90,9 +90,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/01-basic/04-default-blocks/package.json b/examples/01-basic/04-default-blocks/package.json index bec32ddecb..8546777d34 100644 --- a/examples/01-basic/04-default-blocks/package.json +++ b/examples/01-basic/04-default-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/04-default-blocks/src/App.tsx b/examples/01-basic/04-default-blocks/src/App.tsx index dcc6884fc7..0d55d1af3d 100644 --- a/examples/01-basic/04-default-blocks/src/App.tsx +++ b/examples/01-basic/04-default-blocks/src/App.tsx @@ -87,9 +87,8 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { @@ -146,9 +145,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/01-basic/05-removing-default-blocks/package.json b/examples/01-basic/05-removing-default-blocks/package.json index 4970fe877a..72c8e366f4 100644 --- a/examples/01-basic/05-removing-default-blocks/package.json +++ b/examples/01-basic/05-removing-default-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/06-block-manipulation/package.json b/examples/01-basic/06-block-manipulation/package.json index 362df04dd3..9f4c9b0764 100644 --- a/examples/01-basic/06-block-manipulation/package.json +++ b/examples/01-basic/06-block-manipulation/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/07-selection-blocks/package.json b/examples/01-basic/07-selection-blocks/package.json index 8bcccee05f..13106a8e6d 100644 --- a/examples/01-basic/07-selection-blocks/package.json +++ b/examples/01-basic/07-selection-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/07-selection-blocks/src/App.tsx b/examples/01-basic/07-selection-blocks/src/App.tsx index 5251754f5a..811b14e328 100644 --- a/examples/01-basic/07-selection-blocks/src/App.tsx +++ b/examples/01-basic/07-selection-blocks/src/App.tsx @@ -21,9 +21,6 @@ export default function App() { type: "paragraph", content: "Select different blocks to see the JSON change below", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/01-basic/08-ariakit/package.json b/examples/01-basic/08-ariakit/package.json index 75c7713c06..2e9ff90086 100644 --- a/examples/01-basic/08-ariakit/package.json +++ b/examples/01-basic/08-ariakit/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/09-shadcn/package.json b/examples/01-basic/09-shadcn/package.json index 2968b91d44..970aee759f 100644 --- a/examples/01-basic/09-shadcn/package.json +++ b/examples/01-basic/09-shadcn/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "tailwindcss": "^4.1.14", @@ -28,7 +27,7 @@ "@tailwindcss/vite": "^4.1.14", "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/10-localization/package.json b/examples/01-basic/10-localization/package.json index 39828f74fd..5431f4de04 100644 --- a/examples/01-basic/10-localization/package.json +++ b/examples/01-basic/10-localization/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/11-custom-placeholder/package.json b/examples/01-basic/11-custom-placeholder/package.json index 4cde94a58d..b3ca292bfb 100644 --- a/examples/01-basic/11-custom-placeholder/package.json +++ b/examples/01-basic/11-custom-placeholder/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/12-multi-editor/package.json b/examples/01-basic/12-multi-editor/package.json index ab6428dc8b..2408f1d822 100644 --- a/examples/01-basic/12-multi-editor/package.json +++ b/examples/01-basic/12-multi-editor/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/12-multi-editor/src/App.tsx b/examples/01-basic/12-multi-editor/src/App.tsx index bf891fecbf..994e98830f 100644 --- a/examples/01-basic/12-multi-editor/src/App.tsx +++ b/examples/01-basic/12-multi-editor/src/App.tsx @@ -5,14 +5,19 @@ import "@blocknote/mantine/style.css"; import { useCreateBlockNote } from "@blocknote/react"; // Component that creates & renders a BlockNote editor. -function Editor(props: { initialContent?: PartialBlock[] }) { +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 ; + return ( + + ); } export default function App() { @@ -20,6 +25,7 @@ export default function App() { return (
diff --git a/examples/01-basic/13-custom-paste-handler/package.json b/examples/01-basic/13-custom-paste-handler/package.json index 15dc905cb7..8f2fd39c02 100644 --- a/examples/01-basic/13-custom-paste-handler/package.json +++ b/examples/01-basic/13-custom-paste-handler/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/14-editor-scrollable/package.json b/examples/01-basic/14-editor-scrollable/package.json index df436a2643..395196edc9 100644 --- a/examples/01-basic/14-editor-scrollable/package.json +++ b/examples/01-basic/14-editor-scrollable/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/14-shadowdom/.bnexample.json b/examples/01-basic/15-shadowdom/.bnexample.json similarity index 100% rename from examples/01-basic/14-shadowdom/.bnexample.json rename to examples/01-basic/15-shadowdom/.bnexample.json diff --git a/examples/01-basic/14-shadowdom/README.md b/examples/01-basic/15-shadowdom/README.md similarity index 100% rename from examples/01-basic/14-shadowdom/README.md rename to examples/01-basic/15-shadowdom/README.md diff --git a/examples/01-basic/14-shadowdom/index.html b/examples/01-basic/15-shadowdom/index.html similarity index 100% rename from examples/01-basic/14-shadowdom/index.html rename to examples/01-basic/15-shadowdom/index.html diff --git a/examples/01-basic/14-shadowdom/main.tsx b/examples/01-basic/15-shadowdom/main.tsx similarity index 100% rename from examples/01-basic/14-shadowdom/main.tsx rename to examples/01-basic/15-shadowdom/main.tsx diff --git a/examples/01-basic/14-shadowdom/package.json b/examples/01-basic/15-shadowdom/package.json similarity index 80% rename from examples/01-basic/14-shadowdom/package.json rename to examples/01-basic/15-shadowdom/package.json index 07d63662c1..bb80c17b37 100644 --- a/examples/01-basic/14-shadowdom/package.json +++ b/examples/01-basic/15-shadowdom/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/01-basic/14-shadowdom/src/App.tsx b/examples/01-basic/15-shadowdom/src/App.tsx similarity index 100% rename from examples/01-basic/14-shadowdom/src/App.tsx rename to examples/01-basic/15-shadowdom/src/App.tsx diff --git a/examples/01-basic/14-shadowdom/src/vite-env.d.ts b/examples/01-basic/15-shadowdom/src/vite-env.d.ts similarity index 100% rename from examples/01-basic/14-shadowdom/src/vite-env.d.ts rename to examples/01-basic/15-shadowdom/src/vite-env.d.ts diff --git a/examples/01-basic/14-shadowdom/tsconfig.json b/examples/01-basic/15-shadowdom/tsconfig.json similarity index 100% rename from examples/01-basic/14-shadowdom/tsconfig.json rename to examples/01-basic/15-shadowdom/tsconfig.json diff --git a/examples/01-basic/14-shadowdom/vite.config.ts b/examples/01-basic/15-shadowdom/vite.config.ts similarity index 100% rename from examples/01-basic/14-shadowdom/vite.config.ts rename to examples/01-basic/15-shadowdom/vite.config.ts diff --git a/examples/01-basic/16-read-only-editor/.bnexample.json b/examples/01-basic/16-read-only-editor/.bnexample.json new file mode 100644 index 0000000000..6d4a02dd52 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": true, + "author": "matthewlipski", + "tags": ["Basic"] +} 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..677c7f7eed --- /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..c746366d56 --- /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": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file 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..dbe3e6f62d --- /dev/null +++ b/examples/01-basic/16-read-only-editor/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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/16-read-only-editor/vite.config.ts b/examples/01-basic/16-read-only-editor/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/01-basic/16-read-only-editor/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"; +// 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/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..677c7f7eed --- /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..892bec6a77 --- /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": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file 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..dbe3e6f62d --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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/17-no-trailing-block/vite.config.ts b/examples/01-basic/17-no-trailing-block/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/01-basic/17-no-trailing-block/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"; +// 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/testing/package.json b/examples/01-basic/testing/package.json index 909248d480..11ac43e993 100644 --- a/examples/01-basic/testing/package.json +++ b/examples/01-basic/testing/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/02-backend/01-file-uploading/package.json b/examples/02-backend/01-file-uploading/package.json index cd75ed5462..b12ea90ff6 100644 --- a/examples/02-backend/01-file-uploading/package.json +++ b/examples/02-backend/01-file-uploading/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/02-backend/01-file-uploading/src/App.tsx b/examples/02-backend/01-file-uploading/src/App.tsx index c5805f0c40..982d0f37a6 100644 --- a/examples/02-backend/01-file-uploading/src/App.tsx +++ b/examples/02-backend/01-file-uploading/src/App.tsx @@ -33,9 +33,6 @@ export default function App() { { type: "image", }, - { - type: "paragraph", - }, ], uploadFile, }); diff --git a/examples/02-backend/02-saving-loading/package.json b/examples/02-backend/02-saving-loading/package.json index 95f0aa24f7..b38ebfcd69 100644 --- a/examples/02-backend/02-saving-loading/package.json +++ b/examples/02-backend/02-saving-loading/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/02-backend/03-s3/package.json b/examples/02-backend/03-s3/package.json index bd1a31aa01..0ff100fa90 100644 --- a/examples/02-backend/03-s3/package.json +++ b/examples/02-backend/03-s3/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/02-backend/03-s3/src/App.tsx b/examples/02-backend/03-s3/src/App.tsx index 867933afc0..6f6731ec7e 100644 --- a/examples/02-backend/03-s3/src/App.tsx +++ b/examples/02-backend/03-s3/src/App.tsx @@ -81,9 +81,6 @@ export default function App() { { type: "image", }, - { - type: "paragraph", - }, ], uploadFile: async (file) => { /** diff --git a/examples/02-backend/04-rendering-static-documents/package.json b/examples/02-backend/04-rendering-static-documents/package.json index 17927198a8..619434055e 100644 --- a/examples/02-backend/04-rendering-static-documents/package.json +++ b/examples/02-backend/04-rendering-static-documents/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/server-util": "latest" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/02-backend/04-rendering-static-documents/src/App.tsx b/examples/02-backend/04-rendering-static-documents/src/App.tsx index c3121833ae..3a5db9891b 100644 --- a/examples/02-backend/04-rendering-static-documents/src/App.tsx +++ b/examples/02-backend/04-rendering-static-documents/src/App.tsx @@ -22,17 +22,17 @@ This example has the HTML hard-coded, but shows at least how the document will b export default function App() { // This HTML is generated by the ServerBlockNoteEditor.blocksToFullHTML method const html = `
-
-
-
+
+
+

Heading 2

-
-
-
+
+
+

Paragraph

@@ -57,7 +57,7 @@ export default function App() { // 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. -
+
( + + + + {/* 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({ @@ -63,7 +96,7 @@ export default function App() { { type: "image", props: { - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", }, }, { @@ -71,70 +104,13 @@ export default function App() { content: "Notice that the buttons don't appear when the image block above is selected, as it has no inline content.", }, - { - type: "paragraph", - }, ], }); // Renders the editor instance. return ( - ( - - - - {/* Extra button to toggle blue text & background */} - - - - - - - - - - {/* Extra button to toggle code styles */} - - - - - - - - - - - - - - )} - /> + ); } 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 index 73a254bea5..9d554dc51c 100644 --- 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 @@ -10,7 +10,7 @@ "Custom Schemas" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" } } 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 index 64b3352fb2..4b550aaab2 100644 --- 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 @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 2ee1da2771..97b5836bfb 100644 --- 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 @@ -5,6 +5,7 @@ import "@blocknote/mantine/style.css"; import { FormattingToolbarController, blockTypeSelectItems, + useBlockNoteEditor, useCreateBlockNote, BlockTypeSelectItem, FormattingToolbar, @@ -24,6 +25,31 @@ const schema = BlockNoteSchema.create({ }, }); +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({ @@ -43,9 +69,6 @@ export default function App() { content: "Or select text in this alert - the Block Type Select also appears", }, - { - type: "paragraph", - }, ], }); @@ -53,24 +76,7 @@ export default function App() { return ( {/* Replaces the default Formatting Toolbar */} - ( - // Uses the default Formatting Toolbar. - - )} - /> + ); } diff --git a/examples/03-ui-components/04-side-menu-buttons/package.json b/examples/03-ui-components/04-side-menu-buttons/package.json index c3f63ce21a..c3de6a8993 100644 --- a/examples/03-ui-components/04-side-menu-buttons/package.json +++ b/examples/03-ui-components/04-side-menu-buttons/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 96ef099ef3..29a79fbbc9 100644 --- a/examples/03-ui-components/04-side-menu-buttons/src/App.tsx +++ b/examples/03-ui-components/04-side-menu-buttons/src/App.tsx @@ -5,11 +5,20 @@ 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({ @@ -26,24 +35,13 @@ export default function App() { 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/03-ui-components/05-side-menu-drag-handle-items/package.json b/examples/03-ui-components/05-side-menu-drag-handle-items/package.json index 1f9c75200d..15cea76cc7 100644 --- 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 @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 0ff3a07174..ec50019e0c 100644 --- 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 @@ -7,6 +7,7 @@ import { RemoveBlockItem, SideMenu, SideMenuController, + SideMenuProps, useCreateBlockNote, } from "@blocknote/react"; @@ -24,6 +25,10 @@ const CustomDragHandleMenu = () => ( ); +const CustomSideMenu = (props: SideMenuProps) => ( + +); + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -41,20 +46,13 @@ export default function App() { content: "Try resetting this block's type using the new Drag Handle Menu item", }, - { - type: "paragraph", - }, ], }); // Renders the editor instance. return ( - ( - - )} - /> + ); } 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 index 4e1e364c94..0bddd94672 100644 --- 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 @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index c5f65cfdd8..906000401a 100644 --- 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 @@ -56,9 +56,6 @@ export default function App() { type: "paragraph", content: "Notice the new 'Insert Hello World' item - try it out!", }, - { - type: "paragraph", - }, ], }); 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 index e47e7b3eb0..b7226bd31d 100644 --- 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 @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 593e3703cc..c7af9b48a1 100644 --- 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 @@ -48,9 +48,6 @@ export default function App() { type: "paragraph", content: "It's been replaced with a custom component", }, - { - type: "paragraph", - }, ], }); 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 index 1da0bd93ff..1ce117e49c 100644 --- 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 @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 10e0b7f43b..fe4f1e4ed4 100644 --- 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 @@ -22,9 +22,6 @@ export default function App() { type: "paragraph", content: "There are now 5 columns instead of 10", }, - { - type: "paragraph", - }, ], }); 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 index 305056b4ec..4bd33e10e4 100644 --- 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 @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 56e91935f0..88379a6dd0 100644 --- 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 @@ -53,9 +53,6 @@ export default function App() { type: "paragraph", content: "It's been replaced with a custom component", }, - { - type: "paragraph", - }, ], }); 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 index c1fef484c3..637ce38042 100644 --- a/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json +++ b/examples/03-ui-components/10-suggestion-menus-grid-mentions/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 0c2f9f6c21..788ec00ed8 100644 --- 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 @@ -76,9 +76,6 @@ export function App() { type: "paragraph", content: "Press the '@' key to open the mentions menu and add another", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/03-ui-components/11-uppy-file-panel/package.json b/examples/03-ui-components/11-uppy-file-panel/package.json index 8955ce5b72..f5de4f080a 100644 --- a/examples/03-ui-components/11-uppy-file-panel/package.json +++ b/examples/03-ui-components/11-uppy-file-panel/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@uppy/core": "^3.13.1", @@ -37,7 +36,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 8f4ed37a5b..9f01a3c30b 100644 --- a/examples/03-ui-components/11-uppy-file-panel/src/App.tsx +++ b/examples/03-ui-components/11-uppy-file-panel/src/App.tsx @@ -5,6 +5,7 @@ import { FilePanelController, FormattingToolbar, FormattingToolbarController, + FormattingToolbarProps, getFormattingToolbarItems, useCreateBlockNote, } from "@blocknote/react"; @@ -12,6 +13,18 @@ import { 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({ @@ -27,9 +40,6 @@ export default function App() { { type: "image", }, - { - type: "paragraph", - }, ], uploadFile, }); @@ -37,19 +47,7 @@ export default function App() { // Renders the editor instance using a React component. return ( - { - // Replaces default file replace button with one that opens Uppy. - const items = getFormattingToolbarItems(); - items.splice( - items.findIndex((c) => c.key === "replaceFileButton"), - 1, - , - ); - - return {items}; - }} - /> + {/* Replaces default file panel with Uppy one. */} diff --git a/examples/03-ui-components/12-static-formatting-toolbar/package.json b/examples/03-ui-components/12-static-formatting-toolbar/package.json index 8b455ffe0e..180de05193 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/package.json +++ b/examples/03-ui-components/12-static-formatting-toolbar/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index fa61f64dc6..7889f0b342 100644 --- a/examples/03-ui-components/12-static-formatting-toolbar/src/App.tsx +++ b/examples/03-ui-components/12-static-formatting-toolbar/src/App.tsx @@ -17,9 +17,6 @@ export default function App() { type: "paragraph", content: "Check out the static formatting toolbar above!", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/03-ui-components/13-custom-ui/package.json b/examples/03-ui-components/13-custom-ui/package.json index 1ea4e67783..ef56dc0bef 100644 --- a/examples/03-ui-components/13-custom-ui/package.json +++ b/examples/03-ui-components/13-custom-ui/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/03-ui-components/13-custom-ui/src/App.tsx b/examples/03-ui-components/13-custom-ui/src/App.tsx index dd229b1bdd..40974fedbc 100644 --- a/examples/03-ui-components/13-custom-ui/src/App.tsx +++ b/examples/03-ui-components/13-custom-ui/src/App.tsx @@ -27,9 +27,6 @@ export default function App() { type: "paragraph", content: "Welcome to this demo!", }, - { - type: "paragraph", - }, ], }); 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 index 60265f345c..6c2d53320d 100644 --- a/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json +++ b/examples/03-ui-components/14-experimental-mobile-formatting-toolbar/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 62c82ddfe8..47d59e453c 100644 --- 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 @@ -21,9 +21,6 @@ export default function App() { content: "Check out the experimental mobile formatting toolbar by selecting some text (best experienced on a mobile device).", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/03-ui-components/15-advanced-tables/package.json b/examples/03-ui-components/15-advanced-tables/package.json index 38e68d10e4..cf8cbdb701 100644 --- a/examples/03-ui-components/15-advanced-tables/package.json +++ b/examples/03-ui-components/15-advanced-tables/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/03-ui-components/16-link-toolbar-buttons/package.json b/examples/03-ui-components/16-link-toolbar-buttons/package.json index 8e5263cf56..0480356acc 100644 --- a/examples/03-ui-components/16-link-toolbar-buttons/package.json +++ b/examples/03-ui-components/16-link-toolbar-buttons/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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 index 1714e18a0d..52504b56bf 100644 --- a/examples/03-ui-components/16-link-toolbar-buttons/src/App.tsx +++ b/examples/03-ui-components/16-link-toolbar-buttons/src/App.tsx @@ -6,12 +6,32 @@ import { 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({ @@ -40,35 +60,13 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); // Renders the editor instance. return ( - ( - - - - - {/* Extra button to open alert. */} - - - )} - /> + ); } diff --git a/examples/03-ui-components/17-advanced-tables-2/package.json b/examples/03-ui-components/17-advanced-tables-2/package.json index 0b759efc24..72643d1605 100644 --- a/examples/03-ui-components/17-advanced-tables-2/package.json +++ b/examples/03-ui-components/17-advanced-tables-2/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file 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..677c7f7eed --- /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..e925200fe1 --- /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": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file 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..dbe3e6f62d --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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-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..f62ab20bc2 --- /dev/null +++ b/examples/03-ui-components/18-drag-n-drop/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"; +// 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-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..677c7f7eed --- /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..551efbea33 --- /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": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file 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..dbe3e6f62d --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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-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..f62ab20bc2 --- /dev/null +++ b/examples/03-ui-components/19-suggestion-menus-grouping-ordering/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"; +// 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-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..b95bcaf7e0 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/README.md @@ -0,0 +1,16 @@ +# 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..677c7f7eed --- /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..2ecd0a811d --- /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": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file 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..0434ff819b --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/src/App.tsx @@ -0,0 +1,58 @@ +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..dbe3e6f62d --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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-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..f62ab20bc2 --- /dev/null +++ b/examples/03-ui-components/20-portal-elements/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"; +// 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-theming/01-theming-dom-attributes/package.json b/examples/04-theming/01-theming-dom-attributes/package.json index e507bf0891..b577ecace2 100644 --- a/examples/04-theming/01-theming-dom-attributes/package.json +++ b/examples/04-theming/01-theming-dom-attributes/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/01-theming-dom-attributes/src/App.tsx b/examples/04-theming/01-theming-dom-attributes/src/App.tsx index 7068e12388..ccf55a3d5f 100644 --- a/examples/04-theming/01-theming-dom-attributes/src/App.tsx +++ b/examples/04-theming/01-theming-dom-attributes/src/App.tsx @@ -44,9 +44,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/02-changing-font/package.json b/examples/04-theming/02-changing-font/package.json index 012ea4a389..36fbc361ab 100644 --- a/examples/04-theming/02-changing-font/package.json +++ b/examples/04-theming/02-changing-font/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/02-changing-font/src/App.tsx b/examples/04-theming/02-changing-font/src/App.tsx index 4ef3f9a3dd..3626ef9e32 100644 --- a/examples/04-theming/02-changing-font/src/App.tsx +++ b/examples/04-theming/02-changing-font/src/App.tsx @@ -17,9 +17,6 @@ export default function App() { type: "paragraph", content: "You'll see that the font has been changed to Comic Sans MS", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/02-changing-font/src/styles.css b/examples/04-theming/02-changing-font/src/styles.css index 49cad5e5a2..b21029720a 100644 --- a/examples/04-theming/02-changing-font/src/styles.css +++ b/examples/04-theming/02-changing-font/src/styles.css @@ -1,3 +1,3 @@ -.bn-container[data-changing-font-demo] .bn-editor * { +.bn-root[data-changing-font-demo] .bn-editor * { font-family: "Comic Sans MS", sans-serif; } diff --git a/examples/04-theming/03-theming-css/package.json b/examples/04-theming/03-theming-css/package.json index 946f29cfab..56ae428ea5 100644 --- a/examples/04-theming/03-theming-css/package.json +++ b/examples/04-theming/03-theming-css/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/03-theming-css/src/App.tsx b/examples/04-theming/03-theming-css/src/App.tsx index 493ae7d4ed..b15568d810 100644 --- a/examples/04-theming/03-theming-css/src/App.tsx +++ b/examples/04-theming/03-theming-css/src/App.tsx @@ -22,9 +22,6 @@ export default function App() { content: "Press the '/' key - the hovered Slash Menu items are also blue", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/03-theming-css/src/styles.css b/examples/04-theming/03-theming-css/src/styles.css index 8909238e5f..4227097f66 100644 --- a/examples/04-theming/03-theming-css/src/styles.css +++ b/examples/04-theming/03-theming-css/src/styles.css @@ -1,11 +1,10 @@ /* Adds border and shadow to editor */ -.bn-container[data-theming-css-demo] .bn-editor * { +.bn-root[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 { +.bn-root[data-theming-css-demo] .bn-suggestion-menu-item[aria-selected="true"], +.bn-root[data-theming-css-demo] .bn-suggestion-menu-item:hover { background-color: blue; } diff --git a/examples/04-theming/04-theming-css-variables/package.json b/examples/04-theming/04-theming-css-variables/package.json index 2d3fb8fd37..df834c89bf 100644 --- a/examples/04-theming/04-theming-css-variables/package.json +++ b/examples/04-theming/04-theming-css-variables/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/04-theming-css-variables/src/App.tsx b/examples/04-theming/04-theming-css-variables/src/App.tsx index ba45053f04..7ef5ab3f6d 100644 --- a/examples/04-theming/04-theming-css-variables/src/App.tsx +++ b/examples/04-theming/04-theming-css-variables/src/App.tsx @@ -22,9 +22,6 @@ export default function App() { content: "Toggle light/dark mode in the page footer and see the theme change too", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/04-theming-css-variables/src/styles.css b/examples/04-theming/04-theming-css-variables/src/styles.css index 59301c371b..aab8f08d08 100644 --- a/examples/04-theming/04-theming-css-variables/src/styles.css +++ b/examples/04-theming/04-theming-css-variables/src/styles.css @@ -1,5 +1,5 @@ /* Base theme */ -.bn-container[data-theming-css-variables-demo][data-color-scheme] { +.bn-root[data-theming-css-variables-demo][data-color-scheme] { --bn-colors-editor-text: #222222; --bn-colors-editor-background: #ffeeee; --bn-colors-menu-text: #ffffff; @@ -21,7 +21,7 @@ } /* Changes for dark mode */ -.bn-container[data-theming-css-variables-demo][data-color-scheme="dark"] { +.bn-root[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/04-theming/05-theming-css-variables-code/package.json b/examples/04-theming/05-theming-css-variables-code/package.json index e391fc873d..bb64789b86 100644 --- a/examples/04-theming/05-theming-css-variables-code/package.json +++ b/examples/04-theming/05-theming-css-variables-code/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/05-theming-css-variables-code/src/App.tsx b/examples/04-theming/05-theming-css-variables-code/src/App.tsx index 8a02496d7e..0eaa83cfa1 100644 --- a/examples/04-theming/05-theming-css-variables-code/src/App.tsx +++ b/examples/04-theming/05-theming-css-variables-code/src/App.tsx @@ -84,9 +84,6 @@ export default function App() { content: "Toggle light/dark mode in the page footer and see the theme change too", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/06-code-block/package.json b/examples/04-theming/06-code-block/package.json index d1122bd3ac..b9fa69a80a 100644 --- a/examples/04-theming/06-code-block/package.json +++ b/examples/04-theming/06-code-block/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/code-block": "latest" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/06-code-block/src/App.tsx b/examples/04-theming/06-code-block/src/App.tsx index 6111070e37..82d10bae9e 100644 --- a/examples/04-theming/06-code-block/src/App.tsx +++ b/examples/04-theming/06-code-block/src/App.tsx @@ -47,9 +47,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/07-custom-code-block/.bnexample.json b/examples/04-theming/07-custom-code-block/.bnexample.json index 5776d2de67..84166710e3 100644 --- a/examples/04-theming/07-custom-code-block/.bnexample.json +++ b/examples/04-theming/07-custom-code-block/.bnexample.json @@ -5,10 +5,10 @@ "tags": ["Basic"], "dependencies": { "@blocknote/code-block": "latest", - "@shikijs/core": "^3.19.0", - "@shikijs/engine-javascript": "^3.19.0", - "@shikijs/langs-precompiled": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0" + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4" } } diff --git a/examples/04-theming/07-custom-code-block/package.json b/examples/04-theming/07-custom-code-block/package.json index 153b1c160c..aa72035cbc 100644 --- a/examples/04-theming/07-custom-code-block/package.json +++ b/examples/04-theming/07-custom-code-block/package.json @@ -16,22 +16,21 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/code-block": "latest", - "@shikijs/core": "^3.19.0", - "@shikijs/engine-javascript": "^3.19.0", - "@shikijs/langs-precompiled": "^3.19.0", - "@shikijs/themes": "^3.19.0", - "@shikijs/types": "^3.19.0" + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/04-theming/07-custom-code-block/src/App.tsx b/examples/04-theming/07-custom-code-block/src/App.tsx index 32adb82472..8a9c74eac1 100644 --- a/examples/04-theming/07-custom-code-block/src/App.tsx +++ b/examples/04-theming/07-custom-code-block/src/App.tsx @@ -69,9 +69,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); diff --git a/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts b/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts index b596fe2029..de16ace3f8 100644 --- a/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts +++ b/examples/04-theming/07-custom-code-block/src/shiki.bundle.ts @@ -4,7 +4,7 @@ import type { DynamicImportThemeRegistration, HighlighterGeneric, } from "@shikijs/types"; -import { createdBundledHighlighter } from "@shikijs/core"; +import { createBundledHighlighter } from "@shikijs/core"; import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; @@ -24,7 +24,7 @@ const bundledThemes = { "dark-plus": () => import("@shikijs/themes/dark-plus"), } as Record; -const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< +const createHighlighter = /* @__PURE__ */ createBundledHighlighter< BundledLanguage, BundledTheme >({ diff --git a/examples/05-interoperability/01-converting-blocks-to-html/package.json b/examples/05-interoperability/01-converting-blocks-to-html/package.json index 0f0316000f..7b0b7f6409 100644 --- a/examples/05-interoperability/01-converting-blocks-to-html/package.json +++ b/examples/05-interoperability/01-converting-blocks-to-html/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/02-converting-blocks-from-html/package.json b/examples/05-interoperability/02-converting-blocks-from-html/package.json index d62b8e0f42..4c780e56d3 100644 --- a/examples/05-interoperability/02-converting-blocks-from-html/package.json +++ b/examples/05-interoperability/02-converting-blocks-from-html/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/03-converting-blocks-to-md/package.json b/examples/05-interoperability/03-converting-blocks-to-md/package.json index f6b0651c0d..f4d8ae5ada 100644 --- a/examples/05-interoperability/03-converting-blocks-to-md/package.json +++ b/examples/05-interoperability/03-converting-blocks-to-md/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/04-converting-blocks-from-md/package.json b/examples/05-interoperability/04-converting-blocks-from-md/package.json index 3ca496c50d..6a2256cb74 100644 --- a/examples/05-interoperability/04-converting-blocks-from-md/package.json +++ b/examples/05-interoperability/04-converting-blocks-from-md/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json index 3af24b9f75..fec26aef21 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/package.json +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-pdf-exporter": "latest", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx index 7a34932ec8..93d1c04a31 100644 --- a/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx +++ b/examples/05-interoperability/05-converting-blocks-to-pdf/src/App.tsx @@ -226,16 +226,15 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/package.json b/examples/05-interoperability/06-converting-blocks-to-docx/package.json index af8c76e04b..16bb0dfa29 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/package.json +++ b/examples/05-interoperability/06-converting-blocks-to-docx/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-docx-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx index 8c5ae2398f..4b873d9a40 100644 --- a/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx +++ b/examples/05-interoperability/06-converting-blocks-to-docx/src/App.tsx @@ -220,16 +220,15 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/package.json b/examples/05-interoperability/07-converting-blocks-to-odt/package.json index aae892d407..5f3efacc28 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/package.json +++ b/examples/05-interoperability/07-converting-blocks-to-odt/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-odt-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx index fcfa07ff85..7b884ac658 100644 --- a/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx +++ b/examples/05-interoperability/07-converting-blocks-to-odt/src/App.tsx @@ -220,16 +220,15 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json index 10e8836f84..a2f87503bf 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/package.json +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-email-exporter": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx b/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx index 5d3d896b8f..3ba0dddea4 100644 --- a/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx +++ b/examples/05-interoperability/08-converting-blocks-to-react-email/src/App.tsx @@ -216,16 +216,15 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { type: "image", props: { previewWidth: 200, - url: "https://interactive-examples.mdn.mozilla.net/media/cc0-images/grapefruit-slice-332-332.jpg", + url: "https://placehold.co/332x322.jpg", textAlignment: "right", }, }, diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/package.json b/examples/05-interoperability/09-blocks-to-html-static-render/package.json index 52926421b4..58649cab50 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/package.json +++ b/examples/05-interoperability/09-blocks-to-html-static-render/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx b/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx index bd3e3b4c7c..7ef44fc498 100644 --- a/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx +++ b/examples/05-interoperability/09-blocks-to-html-static-render/src/App.tsx @@ -90,9 +90,8 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { @@ -149,9 +148,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); @@ -192,7 +188,7 @@ export default function App() { etc. It's easiest to just copy the class names and HTML attributes from an actual BlockNote editor. */}
diff --git a/examples/05-interoperability/10-static-html-render/package.json b/examples/05-interoperability/10-static-html-render/package.json index 6343ac6efc..7da684540f 100644 --- a/examples/05-interoperability/10-static-html-render/package.json +++ b/examples/05-interoperability/10-static-html-render/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/05-interoperability/10-static-html-render/src/App.tsx b/examples/05-interoperability/10-static-html-render/src/App.tsx index 3389c4bf47..f1f4eb4d42 100644 --- a/examples/05-interoperability/10-static-html-render/src/App.tsx +++ b/examples/05-interoperability/10-static-html-render/src/App.tsx @@ -1,7 +1,6 @@ import "@blocknote/core/fonts/inter.css"; import "@blocknote/mantine/style.css"; import { useCreateBlockNote, usePrefersColorScheme } from "@blocknote/react"; -import { useRef, useEffect } from "react"; export default function App() { // Creates a new editor instance. @@ -87,9 +86,8 @@ export default function App() { { 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", + url: "https://placehold.co/332x322.jpg", + caption: "From https://placehold.co/332x322.jpg", }, }, { @@ -146,9 +144,6 @@ export default function App() { }, ], }, - { - type: "paragraph", - }, ], }); @@ -159,7 +154,7 @@ export default function App() { // Renders the exported static HTML from the editor. return (
diff --git a/examples/06-custom-schema/01-alert-block/.bnexample.json b/examples/06-custom-schema/01-alert-block/.bnexample.json index 955f240be5..1354e61187 100644 --- a/examples/06-custom-schema/01-alert-block/.bnexample.json +++ b/examples/06-custom-schema/01-alert-block/.bnexample.json @@ -10,7 +10,7 @@ "Slash Menu" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" } } diff --git a/examples/06-custom-schema/01-alert-block/package.json b/examples/06-custom-schema/01-alert-block/package.json index b02510a70f..2ef9cadd20 100644 --- a/examples/06-custom-schema/01-alert-block/package.json +++ b/examples/06-custom-schema/01-alert-block/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/01-alert-block/src/App.tsx b/examples/06-custom-schema/01-alert-block/src/App.tsx index 929f5b8459..34a3afba7b 100644 --- a/examples/06-custom-schema/01-alert-block/src/App.tsx +++ b/examples/06-custom-schema/01-alert-block/src/App.tsx @@ -32,9 +32,6 @@ export default function App() { type: "paragraph", content: "Click the '!' icon to change the alert type", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json index ea836ebabe..21e338460a 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/package.json +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx b/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx index 4339153441..c1e1a5c412 100644 --- a/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx +++ b/examples/06-custom-schema/02-suggestion-menus-mentions/src/App.tsx @@ -75,9 +75,6 @@ export function App() { type: "paragraph", content: "Press the '@' key to open the mentions menu and add another", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/03-font-style/package.json b/examples/06-custom-schema/03-font-style/package.json index ab031bf380..b784dc08b2 100644 --- a/examples/06-custom-schema/03-font-style/package.json +++ b/examples/06-custom-schema/03-font-style/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/03-font-style/src/App.tsx b/examples/06-custom-schema/03-font-style/src/App.tsx index 4cae9935b1..c813aaf713 100644 --- a/examples/06-custom-schema/03-font-style/src/App.tsx +++ b/examples/06-custom-schema/03-font-style/src/App.tsx @@ -60,6 +60,36 @@ const SetFontStyleButton = () => { ); }; +const CustomFormattingToolbar = () => ( + + + + + + + + + + + {/* Adds SetFontStyleButton */} + + + + + + + + + + + + + +); + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -91,64 +121,13 @@ export default function App() { content: "Highlight some text to open the Formatting Toolbar and change the font elsewhere", }, - { - type: "paragraph", - }, ], }); return ( {/* Replaces the default Formatting Toolbar. */} - ( - - - - - - - - - - - {/* Adds SetFontStyleButton */} - - - - - - - - - - - - - - )} - /> + ); } diff --git a/examples/06-custom-schema/04-pdf-file-block/.bnexample.json b/examples/06-custom-schema/04-pdf-file-block/.bnexample.json index f3f9f4d51b..c9ed35fa87 100644 --- a/examples/06-custom-schema/04-pdf-file-block/.bnexample.json +++ b/examples/06-custom-schema/04-pdf-file-block/.bnexample.json @@ -10,7 +10,7 @@ "Slash Menu" ], "dependencies": { - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "react-icons": "^5.5.0" }, "pro": true diff --git a/examples/06-custom-schema/04-pdf-file-block/package.json b/examples/06-custom-schema/04-pdf-file-block/package.json index d5f5615cd0..cd1de2d12f 100644 --- a/examples/06-custom-schema/04-pdf-file-block/package.json +++ b/examples/06-custom-schema/04-pdf-file-block/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "react-icons": "^5.5.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/04-pdf-file-block/src/App.tsx b/examples/06-custom-schema/04-pdf-file-block/src/App.tsx index 3c244a2840..c5a69d9b4b 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/App.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/App.tsx @@ -67,9 +67,6 @@ export default function App() { type: "paragraph", content: "Press the '/' key to open the Slash Menu and add another PDF", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx index f17619d8ba..8ce605ea70 100644 --- a/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx +++ b/examples/06-custom-schema/04-pdf-file-block/src/PDF.tsx @@ -10,14 +10,7 @@ import { RiFilePdfFill } from "react-icons/ri"; import "./styles.css"; export const PDFPreview = ( - props: Omit< - ReactCustomBlockRenderProps< - FileBlockConfig["type"], - FileBlockConfig["propSchema"], - FileBlockConfig["content"] - >, - "contentRef" - >, + props: Omit, "contentRef">, ) => { return ( { + const editor = useBlockNoteEditor< + typeof schema.blockSchema, + typeof schema.inlineContentSchema, + typeof schema.styleSchema + >(); + + return ( + // Uses the default Formatting Toolbar. + + ); +}; + // Slash menu item to insert an Alert block const insertAlert = (editor: typeof schema.BlockNoteEditor) => ({ title: "Alert", @@ -75,9 +101,6 @@ export default function App() { content: "Or select some text to see the alert in the Formatting Toolbar's Block Type Select", }, - { - type: "paragraph", - }, ], }); @@ -85,24 +108,7 @@ export default function App() { return ( {/* Replaces the default Formatting Toolbar */} - ( - // Uses the default Formatting Toolbar. - - )} - /> + {/* Replaces the default Slash Menu. */} ' icon to show/hide its children", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/07-configuring-blocks/package.json b/examples/06-custom-schema/07-configuring-blocks/package.json index edf107c487..fc2e9a52cb 100644 --- a/examples/06-custom-schema/07-configuring-blocks/package.json +++ b/examples/06-custom-schema/07-configuring-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/07-configuring-blocks/src/App.tsx b/examples/06-custom-schema/07-configuring-blocks/src/App.tsx index 491dc8a1c1..47db15f007 100644 --- a/examples/06-custom-schema/07-configuring-blocks/src/App.tsx +++ b/examples/06-custom-schema/07-configuring-blocks/src/App.tsx @@ -32,9 +32,6 @@ export default function App() { content: "Notice how only heading levels 1-3 are available, and toggle headings are not shown.", }, - { - type: "paragraph", - }, ], }); diff --git a/examples/06-custom-schema/08-non-editable-block/.bnexample.json b/examples/06-custom-schema/08-non-editable-block/.bnexample.json new file mode 100644 index 0000000000..c94d1e9154 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/.bnexample.json @@ -0,0 +1,6 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Intermediate", "Blocks", "Custom Schemas"] +} diff --git a/examples/06-custom-schema/08-non-editable-block/README.md b/examples/06-custom-schema/08-non-editable-block/README.md new file mode 100644 index 0000000000..9c7cce19d1 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/README.md @@ -0,0 +1,8 @@ +# Non-Editable Block + +In this example, we create a custom block which renders a simple HTML paragraph with placeholder text. The block has no editable content. + +**Relevant Docs:** + +- [Custom Blocks](/docs/features/custom-schemas/custom-blocks) +- [Editor Setup](/docs/getting-started/editor-setup) diff --git a/examples/06-custom-schema/08-non-editable-block/index.html b/examples/06-custom-schema/08-non-editable-block/index.html new file mode 100644 index 0000000000..9b55422066 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/index.html @@ -0,0 +1,14 @@ + + + + + Non-Editable Block + + + +
+ + + diff --git a/examples/06-custom-schema/08-non-editable-block/main.tsx b/examples/06-custom-schema/08-non-editable-block/main.tsx new file mode 100644 index 0000000000..677c7f7eed --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-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/06-custom-schema/08-non-editable-block/package.json b/examples/06-custom-schema/08-non-editable-block/package.json new file mode 100644 index 0000000000..a988601ed7 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/package.json @@ -0,0 +1,30 @@ +{ + "name": "@blocknote/example-custom-schema-non-editable-block", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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": "^8.0.8" + } +} \ No newline at end of file diff --git a/examples/06-custom-schema/08-non-editable-block/src/App.tsx b/examples/06-custom-schema/08-non-editable-block/src/App.tsx new file mode 100644 index 0000000000..ca7a4cd8d0 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/src/App.tsx @@ -0,0 +1,35 @@ +import { BlockNoteSchema } 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 { createNonEditableBlock } from "./NonEditableBlock"; + +// Our schema with block specs, which contain the configs and implementations for +// blocks that we want our editor to use. +const schema = BlockNoteSchema.create().extend({ + blockSpecs: { + // Creates an instance of the Non-Editable block and adds it to the schema. + nonEditable: createNonEditableBlock(), + }, +}); + +export default function App() { + // Creates a new editor instance. + const editor = useCreateBlockNote({ + schema, + initialContent: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "nonEditable", + }, + ], + }); + + // Renders the editor instance. + return ; +} diff --git a/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx b/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx new file mode 100644 index 0000000000..a930c21f74 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/src/NonEditableBlock.tsx @@ -0,0 +1,13 @@ +import { createReactBlockSpec } from "@blocknote/react"; + +// The Non-Editable block. +export const createNonEditableBlock = createReactBlockSpec( + { + type: "nonEditable", + propSchema: {}, + content: "none", + }, + { + render: () =>

This is a non-editable block.

, + }, +); diff --git a/examples/06-custom-schema/08-non-editable-block/tsconfig.json b/examples/06-custom-schema/08-non-editable-block/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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/06-custom-schema/08-non-editable-block/vite.config.ts b/examples/06-custom-schema/08-non-editable-block/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/06-custom-schema/08-non-editable-block/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"; +// 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/06-custom-schema/draggable-inline-content/package.json b/examples/06-custom-schema/draggable-inline-content/package.json index 98db481029..3689f5390c 100644 --- a/examples/06-custom-schema/draggable-inline-content/package.json +++ b/examples/06-custom-schema/draggable-inline-content/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/react-custom-blocks/package.json b/examples/06-custom-schema/react-custom-blocks/package.json index 0372b1c809..b31875e4da 100644 --- a/examples/06-custom-schema/react-custom-blocks/package.json +++ b/examples/06-custom-schema/react-custom-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/react-custom-blocks/src/App.tsx b/examples/06-custom-schema/react-custom-blocks/src/App.tsx index dd0573877d..15ad3ebd36 100644 --- a/examples/06-custom-schema/react-custom-blocks/src/App.tsx +++ b/examples/06-custom-schema/react-custom-blocks/src/App.tsx @@ -81,8 +81,7 @@ const simpleImageBlock = createReactBlockSpec( type: "simpleImage", propSchema: { src: { - default: - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + default: "https://placehold.co/800x540.png", }, }, content: "none", @@ -142,7 +141,7 @@ export default function App() { { type: "simpleImage", props: { - src: "https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg", + src: "https://placehold.co/332x322.jpg", }, }, { diff --git a/examples/06-custom-schema/react-custom-inline-content/package.json b/examples/06-custom-schema/react-custom-inline-content/package.json index 98abca8461..5e2ed6d99b 100644 --- a/examples/06-custom-schema/react-custom-inline-content/package.json +++ b/examples/06-custom-schema/react-custom-inline-content/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/06-custom-schema/react-custom-styles/package.json b/examples/06-custom-schema/react-custom-styles/package.json index eb88b335ff..3815485703 100644 --- a/examples/06-custom-schema/react-custom-styles/package.json +++ b/examples/06-custom-schema/react-custom-styles/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/01-partykit/package.json b/examples/07-collaboration/01-partykit/package.json index c29a4981a9..9f4a8d0870 100644 --- a/examples/07-collaboration/01-partykit/package.json +++ b/examples/07-collaboration/01-partykit/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/02-liveblocks/.bnexample.json b/examples/07-collaboration/02-liveblocks/.bnexample.json index b212ead624..f74c6d26bb 100644 --- a/examples/07-collaboration/02-liveblocks/.bnexample.json +++ b/examples/07-collaboration/02-liveblocks/.bnexample.json @@ -4,11 +4,11 @@ "author": "yousefed", "tags": ["Advanced", "Saving/Loading", "Collaboration"], "dependencies": { - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", + "@liveblocks/client": "^3.17.0", + "@liveblocks/react": "^3.17.0", + "@liveblocks/react-blocknote": "^3.17.0", + "@liveblocks/react-tiptap": "^3.17.0", + "@liveblocks/react-ui": "^3.17.0", "yjs": "^13.6.27" } } diff --git a/examples/07-collaboration/02-liveblocks/package.json b/examples/07-collaboration/02-liveblocks/package.json index 42dac31b38..79dd475fc8 100644 --- a/examples/07-collaboration/02-liveblocks/package.json +++ b/examples/07-collaboration/02-liveblocks/package.json @@ -16,22 +16,21 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", - "@liveblocks/client": "3.7.1-tiptap3", - "@liveblocks/react": "3.7.1-tiptap3", - "@liveblocks/react-blocknote": "3.7.1-tiptap3", - "@liveblocks/react-tiptap": "3.7.1-tiptap3", - "@liveblocks/react-ui": "3.7.1-tiptap3", + "@liveblocks/client": "^3.17.0", + "@liveblocks/react": "^3.17.0", + "@liveblocks/react-blocknote": "^3.17.0", + "@liveblocks/react-tiptap": "^3.17.0", + "@liveblocks/react-ui": "^3.17.0", "yjs": "^13.6.27" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/03-y-sweet/package.json b/examples/07-collaboration/03-y-sweet/package.json index ca2e4b0097..8ca1ec5d75 100644 --- a/examples/07-collaboration/03-y-sweet/package.json +++ b/examples/07-collaboration/03-y-sweet/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@y-sweet/react": "^0.6.3" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/04-electric-sql/package.json b/examples/07-collaboration/04-electric-sql/package.json index dd9d538875..8f0c69c80b 100644 --- a/examples/07-collaboration/04-electric-sql/package.json +++ b/examples/07-collaboration/04-electric-sql/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/05-comments/.bnexample.json b/examples/07-collaboration/05-comments/.bnexample.json index 09ecc7196c..f2e0c026a9 100644 --- a/examples/07-collaboration/05-comments/.bnexample.json +++ b/examples/07-collaboration/05-comments/.bnexample.json @@ -5,6 +5,6 @@ "tags": ["Advanced", "Comments", "Collaboration"], "dependencies": { "@y-sweet/react": "^0.6.3", - "@mantine/core": "^8.3.11" + "@mantine/core": "^9.0.2" } } diff --git a/examples/07-collaboration/05-comments/package.json b/examples/07-collaboration/05-comments/package.json index 27897406ce..7736c432b4 100644 --- a/examples/07-collaboration/05-comments/package.json +++ b/examples/07-collaboration/05-comments/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@y-sweet/react": "^0.6.3" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json b/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json index a80bff5aba..ff82fe290f 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json +++ b/examples/07-collaboration/06-comments-with-sidebar/.bnexample.json @@ -6,6 +6,6 @@ "dependencies": { "y-partykit": "^0.0.25", "yjs": "^13.6.27", - "@mantine/core": "^8.3.11" + "@mantine/core": "^9.0.2" } } diff --git a/examples/07-collaboration/06-comments-with-sidebar/package.json b/examples/07-collaboration/06-comments-with-sidebar/package.json index c1a6c49f9a..67a5504590 100644 --- a/examples/07-collaboration/06-comments-with-sidebar/package.json +++ b/examples/07-collaboration/06-comments-with-sidebar/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/07-ghost-writer/package.json b/examples/07-collaboration/07-ghost-writer/package.json index a45bc3a305..26e4956fab 100644 --- a/examples/07-collaboration/07-ghost-writer/package.json +++ b/examples/07-collaboration/07-ghost-writer/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/08-forking/package.json b/examples/07-collaboration/08-forking/package.json index 08ca3f389d..3d82fc59ba 100644 --- a/examples/07-collaboration/08-forking/package.json +++ b/examples/07-collaboration/08-forking/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "y-partykit": "^0.0.25", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/07-collaboration/09-comments-testing/.bnexample.json b/examples/07-collaboration/09-comments-testing/.bnexample.json new file mode 100644 index 0000000000..5d7d986420 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/.bnexample.json @@ -0,0 +1,9 @@ +{ + "playground": true, + "docs": false, + "author": "matthewlipski", + "tags": ["Advanced", "Comments", "Testing"], + "dependencies": { + "yjs": "^13.6.27" + } +} diff --git a/examples/07-collaboration/09-comments-testing/README.md b/examples/07-collaboration/09-comments-testing/README.md new file mode 100644 index 0000000000..b59f2ecd1b --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/README.md @@ -0,0 +1,3 @@ +# Comments Testing + +A minimal comments example used for end-to-end testing. Uses a local Y.Doc (no collaboration provider) with a single hardcoded editor user. diff --git a/examples/07-collaboration/09-comments-testing/index.html b/examples/07-collaboration/09-comments-testing/index.html new file mode 100644 index 0000000000..f50976be79 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/index.html @@ -0,0 +1,14 @@ + + + + + Comments Testing + + + +
+ + + diff --git a/examples/07-collaboration/09-comments-testing/main.tsx b/examples/07-collaboration/09-comments-testing/main.tsx new file mode 100644 index 0000000000..677c7f7eed --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/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/07-collaboration/09-comments-testing/package.json b/examples/07-collaboration/09-comments-testing/package.json new file mode 100644 index 0000000000..c31e6c15c3 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/package.json @@ -0,0 +1,31 @@ +{ + "name": "@blocknote/example-collaboration-comments-testing", + "description": "AUTO-GENERATED FILE, DO NOT EDIT DIRECTLY", + "type": "module", + "private": true, + "version": "0.12.4", + "scripts": { + "start": "vite", + "dev": "vite", + "build:prod": "tsc && vite build", + "preview": "vite 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", + "yjs": "^13.6.27" + }, + "devDependencies": { + "@types/react": "^19.2.3", + "@types/react-dom": "^19.2.3", + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" + } +} \ No newline at end of file diff --git a/examples/07-collaboration/09-comments-testing/src/App.tsx b/examples/07-collaboration/09-comments-testing/src/App.tsx new file mode 100644 index 0000000000..3bada358c1 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/src/App.tsx @@ -0,0 +1,44 @@ +"use client"; + +import { + CommentsExtension, + DefaultThreadStoreAuth, + YjsThreadStore, +} from "@blocknote/core/comments"; +import { BlockNoteView } from "@blocknote/mantine"; +import "@blocknote/mantine/style.css"; +import { useCreateBlockNote } from "@blocknote/react"; +import { useMemo } from "react"; +import * as Y from "yjs"; + +const USER = { + id: "1", + username: "John Doe", + avatarUrl: "https://placehold.co/100x100?text=John", + role: "editor" as const, +}; + +async function resolveUsers(userIds: string[]) { + return [USER].filter((user) => userIds.includes(user.id)); +} + +export default function App() { + const doc = useMemo(() => new Y.Doc(), []); + + const threadStore = useMemo(() => { + return new YjsThreadStore( + USER.id, + doc.getMap("threads"), + new DefaultThreadStoreAuth(USER.id, USER.role), + ); + }, [doc]); + + const editor = useCreateBlockNote( + { + extensions: [CommentsExtension({ threadStore, resolveUsers })], + }, + [threadStore], + ); + + return ; +} diff --git a/examples/07-collaboration/09-comments-testing/tsconfig.json b/examples/07-collaboration/09-comments-testing/tsconfig.json new file mode 100644 index 0000000000..dbe3e6f62d --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/tsconfig.json @@ -0,0 +1,36 @@ +{ + "__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": "bundler", + "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/07-collaboration/09-comments-testing/vite.config.ts b/examples/07-collaboration/09-comments-testing/vite.config.ts new file mode 100644 index 0000000000..f62ab20bc2 --- /dev/null +++ b/examples/07-collaboration/09-comments-testing/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"; +// 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/08-extensions/01-tiptap-arrow-conversion/package.json b/examples/08-extensions/01-tiptap-arrow-conversion/package.json index 7eaeaf3eaa..c781441abe 100644 --- a/examples/08-extensions/01-tiptap-arrow-conversion/package.json +++ b/examples/08-extensions/01-tiptap-arrow-conversion/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@tiptap/core": "^3.13.0" @@ -26,7 +25,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/01-minimal/.bnexample.json b/examples/09-ai/01-minimal/.bnexample.json index 30d6def791..9aede450f7 100644 --- a/examples/09-ai/01-minimal/.bnexample.json +++ b/examples/09-ai/01-minimal/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/01-minimal/package.json b/examples/09-ai/01-minimal/package.json index b244123bd7..b4ef7599fc 100644 --- a/examples/09-ai/01-minimal/package.json +++ b/examples/09-ai/01-minimal/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/01-minimal/src/App.tsx b/examples/09-ai/01-minimal/src/App.tsx index 37417d292b..3ada0eea97 100644 --- a/examples/09-ai/01-minimal/src/App.tsx +++ b/examples/09-ai/01-minimal/src/App.tsx @@ -22,12 +22,27 @@ import { en as aiEn } from "@blocknote/xl-ai/locales"; import "@blocknote/xl-ai/style.css"; import { DefaultChatTransport } from "ai"; -import { useEffect } from "react"; import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -84,54 +99,23 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + - {/* We disabled the default SlashMenu with `slashMenu=false` - and replace it for one with an AI option (defined below). + {/* We disabled the default SlashMenu with `slashMenu=false` + and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/02-playground/.bnexample.json b/examples/09-ai/02-playground/.bnexample.json index 30d6def791..9aede450f7 100644 --- a/examples/09-ai/02-playground/.bnexample.json +++ b/examples/09-ai/02-playground/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/02-playground/package.json b/examples/09-ai/02-playground/package.json index 9bfc984a4a..98f9c40219 100644 --- a/examples/09-ai/02-playground/package.json +++ b/examples/09-ai/02-playground/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/02-playground/src/App.tsx b/examples/09-ai/02-playground/src/App.tsx index ace8b47d79..008b17b40a 100644 --- a/examples/09-ai/02-playground/src/App.tsx +++ b/examples/09-ai/02-playground/src/App.tsx @@ -33,6 +33,20 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + ...getAISlashMenuItems(editor), +]; + export default function App() { const [model, setModel] = useState( "groq.chat/llama-3.3-70b-versatile", @@ -129,52 +143,23 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/03-custom-ai-menu-items/.bnexample.json b/examples/09-ai/03-custom-ai-menu-items/.bnexample.json index a026ef1ee5..9a91d82062 100644 --- a/examples/09-ai/03-custom-ai-menu-items/.bnexample.json +++ b/examples/09-ai/03-custom-ai-menu-items/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "react-icons": "^5.5.0" } diff --git a/examples/09-ai/03-custom-ai-menu-items/package.json b/examples/09-ai/03-custom-ai-menu-items/package.json index 1385ceab9b..ee4c5d2163 100644 --- a/examples/09-ai/03-custom-ai-menu-items/package.json +++ b/examples/09-ai/03-custom-ai-menu-items/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/03-custom-ai-menu-items/src/App.tsx b/examples/09-ai/03-custom-ai-menu-items/src/App.tsx index 8eee964b9f..3646cc8d9e 100644 --- a/examples/09-ai/03-custom-ai-menu-items/src/App.tsx +++ b/examples/09-ai/03-custom-ai-menu-items/src/App.tsx @@ -30,6 +30,63 @@ import { addRelatedTopics, makeInformal } from "./customAIMenuItems"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +function CustomAIMenu() { + return ( + , + aiResponseStatus: + | "user-input" + | "thinking" + | "ai-writing" + | "error" + | "user-reviewing" + | "closed", + ) => { + if (aiResponseStatus === "user-input") { + // Returns different items based on whether the AI Menu was + // opened via the Formatting Toolbar or the Slash Menu. + if (editor.getSelection()) { + return [ + // Gets the default AI Menu items + ...getDefaultAIMenuItems(editor, aiResponseStatus), + // Adds our custom item to make the text more casual. + // Only appears when the AI Menu is opened via the + // Formatting Toolbar. + makeInformal(editor), + ]; + } else { + return [ + // Gets the default AI Menu items + ...getDefaultAIMenuItems(editor, aiResponseStatus), + // Adds our custom item to find related topics. Only + // appears when the AI Menu is opened via the Slash + // Menu. + addRelatedTopics(editor), + ]; + } + } + // for other states, return the default items + return getDefaultAIMenuItems(editor, aiResponseStatus); + }} + /> + ); +} + +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -85,95 +142,23 @@ export default function App() { as well as our custom ones. */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
); } - -function CustomAIMenu() { - return ( - , - aiResponseStatus: - | "user-input" - | "thinking" - | "ai-writing" - | "error" - | "user-reviewing" - | "closed", - ) => { - if (aiResponseStatus === "user-input") { - // Returns different items based on whether the AI Menu was - // opened via the Formatting Toolbar or the Slash Menu. - if (editor.getSelection()) { - return [ - // Gets the default AI Menu items - ...getDefaultAIMenuItems(editor, aiResponseStatus), - // Adds our custom item to make the text more casual. - // Only appears when the AI Menu is opened via the - // Formatting Toolbar. - makeInformal(editor), - ]; - } else { - return [ - // Gets the default AI Menu items - ...getDefaultAIMenuItems(editor, aiResponseStatus), - // Adds our custom item to find related topics. Only - // appears when the AI Menu is opened via the Slash - // Menu. - addRelatedTopics(editor), - ]; - } - } - // for other states, return the default items - return getDefaultAIMenuItems(editor, aiResponseStatus); - }} - /> - ); -} - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/04-with-collaboration/.bnexample.json b/examples/09-ai/04-with-collaboration/.bnexample.json index 922d7f719e..83bed82fe4 100644 --- a/examples/09-ai/04-with-collaboration/.bnexample.json +++ b/examples/09-ai/04-with-collaboration/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "y-partykit": "^0.0.25", "yjs": "^13.6.27" diff --git a/examples/09-ai/04-with-collaboration/package.json b/examples/09-ai/04-with-collaboration/package.json index fe17faaa59..e75864a84f 100644 --- a/examples/09-ai/04-with-collaboration/package.json +++ b/examples/09-ai/04-with-collaboration/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -29,7 +28,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/04-with-collaboration/src/App.tsx b/examples/09-ai/04-with-collaboration/src/App.tsx index 3fd8076ccd..9073141352 100644 --- a/examples/09-ai/04-with-collaboration/src/App.tsx +++ b/examples/09-ai/04-with-collaboration/src/App.tsx @@ -58,6 +58,22 @@ if (isGhostWriting) { const ghostContent = "This demo shows a two-way sync of documents. It allows you to test collaboration features, and see how stable the editor is. "; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { const [numGhostWriters, setNumGhostWriters] = useState(1); const [isPaused, setIsPaused] = useState(false); @@ -176,17 +192,22 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + /> {!isGhostWriting && ( @@ -205,39 +226,3 @@ export default function App() { ); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/05-manual-execution/.bnexample.json b/examples/09-ai/05-manual-execution/.bnexample.json index c5b86534d1..890b2909fe 100644 --- a/examples/09-ai/05-manual-execution/.bnexample.json +++ b/examples/09-ai/05-manual-execution/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5", "y-partykit": "^0.0.25", "yjs": "^13.6.27" diff --git a/examples/09-ai/05-manual-execution/package.json b/examples/09-ai/05-manual-execution/package.json index b23f7678a0..56ee0692fd 100644 --- a/examples/09-ai/05-manual-execution/package.json +++ b/examples/09-ai/05-manual-execution/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -29,7 +28,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/06-client-side-transport/.bnexample.json b/examples/09-ai/06-client-side-transport/.bnexample.json index 1c7e871335..0ac2b679fc 100644 --- a/examples/09-ai/06-client-side-transport/.bnexample.json +++ b/examples/09-ai/06-client-side-transport/.bnexample.json @@ -6,7 +6,7 @@ "dependencies": { "@ai-sdk/groq": "^3.0.2", "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/06-client-side-transport/package.json b/examples/09-ai/06-client-side-transport/package.json index 4a143fbef3..94250e9f3c 100644 --- a/examples/09-ai/06-client-side-transport/package.json +++ b/examples/09-ai/06-client-side-transport/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@ai-sdk/groq": "^3.0.2", @@ -28,7 +27,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/06-client-side-transport/src/App.tsx b/examples/09-ai/06-client-side-transport/src/App.tsx index ca7b64518e..14ed72abdb 100644 --- a/examples/09-ai/06-client-side-transport/src/App.tsx +++ b/examples/09-ai/06-client-side-transport/src/App.tsx @@ -28,6 +28,22 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + // We define the model directly in our app using the Vercel AI SDK const model = createGroq({ // We supply a custom fetch function so that requests are routed through our proxy server @@ -97,54 +113,23 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/09-ai/07-server-persistence/.bnexample.json b/examples/09-ai/07-server-persistence/.bnexample.json index 35815d0d21..12b79358ef 100644 --- a/examples/09-ai/07-server-persistence/.bnexample.json +++ b/examples/09-ai/07-server-persistence/.bnexample.json @@ -5,7 +5,7 @@ "tags": ["AI", "llm"], "dependencies": { "@blocknote/xl-ai": "latest", - "@mantine/core": "^8.3.11", + "@mantine/core": "^9.0.2", "ai": "^6.0.5" } } diff --git a/examples/09-ai/07-server-persistence/package.json b/examples/09-ai/07-server-persistence/package.json index b379364817..bbcb69e15d 100644 --- a/examples/09-ai/07-server-persistence/package.json +++ b/examples/09-ai/07-server-persistence/package.json @@ -16,9 +16,8 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@mantine/core": "^9.0.2", + "@mantine/hooks": "^9.0.2", "react": "^19.2.3", "react-dom": "^19.2.3", "@blocknote/xl-ai": "latest", @@ -27,7 +26,7 @@ "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/09-ai/07-server-persistence/src/App.tsx b/examples/09-ai/07-server-persistence/src/App.tsx index a22d9c9e1c..1d07beafac 100644 --- a/examples/09-ai/07-server-persistence/src/App.tsx +++ b/examples/09-ai/07-server-persistence/src/App.tsx @@ -26,6 +26,22 @@ import { getEnv } from "./getEnv"; const BASE_URL = getEnv("BLOCKNOTE_AI_SERVER_BASE_URL") || "https://localhost:3000/ai"; +// Formatting toolbar with the `AIToolbarButton` added +const FormattingToolbarWithAI = () => ( + + {...getFormattingToolbarItems()} + {/* Add the AI button */} + + +); + +// Slash menu items with the AI option added +const getSlashMenuItemsWithAI = (editor: BlockNoteEditor) => [ + ...getDefaultReactSlashMenuItems(editor), + // add the default AI slash menu items, or define your own + ...getAISlashMenuItems(editor), +]; + export default function App() { // Creates a new editor instance. const editor = useCreateBlockNote({ @@ -112,54 +128,23 @@ export default function App() { {/* Add the AI Command menu to the editor */} - {/* We disabled the default formatting toolbar with `formattingToolbar=false` - and replace it for one with an "AI button" (defined below). + {/* We disabled the default formatting toolbar with `formattingToolbar=false` + and replace it for one with an "AI button" (defined below). (See "Formatting Toolbar" in docs) */} - + {/* We disabled the default SlashMenu with `slashMenu=false` and replace it for one with an AI option (defined below). (See "Suggestion Menus" in docs) */} - + + filterSuggestionItems(getSlashMenuItemsWithAI(editor), query) + } + />
); } - -// Formatting toolbar with the `AIToolbarButton` added -function FormattingToolbarWithAI() { - return ( - ( - - {...getFormattingToolbarItems()} - {/* Add the AI button */} - - - )} - /> - ); -} - -// Slash menu with the AI option added -function SuggestionMenuWithAI(props: { - editor: BlockNoteEditor; -}) { - return ( - - filterSuggestionItems( - [ - ...getDefaultReactSlashMenuItems(props.editor), - // add the default AI slash menu items, or define your own - ...getAISlashMenuItems(props.editor), - ], - query, - ) - } - /> - ); -} diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/package.json b/examples/vanilla-js/react-vanilla-custom-blocks/package.json index caf42e11e5..bb2e5beeee 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/package.json +++ b/examples/vanilla-js/react-vanilla-custom-blocks/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx index 3386861a88..b0711912e3 100644 --- a/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx +++ b/examples/vanilla-js/react-vanilla-custom-blocks/src/App.tsx @@ -115,8 +115,7 @@ const simpleImageBlock = createBlockSpec( type: "simpleImage", propSchema: { src: { - default: - "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + default: "https://placehold.co/800x540.png", }, }, content: "none", @@ -201,7 +200,7 @@ export default function App() { { type: "simpleImage", props: { - src: "https://t3.ftcdn.net/jpg/02/48/42/64/360_F_248426448_NVKLywWqArG2ADUxDq6QprtIzsF82dMF.jpg", + src: "https://placehold.co/800x540.png", }, }, { diff --git a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json index 2eb0b9a64f..e065088442 100644 --- a/examples/vanilla-js/react-vanilla-custom-inline-content/package.json +++ b/examples/vanilla-js/react-vanilla-custom-inline-content/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/examples/vanilla-js/react-vanilla-custom-styles/package.json b/examples/vanilla-js/react-vanilla-custom-styles/package.json index f192b05a60..1f2691e014 100644 --- a/examples/vanilla-js/react-vanilla-custom-styles/package.json +++ b/examples/vanilla-js/react-vanilla-custom-styles/package.json @@ -16,16 +16,15 @@ "@blocknote/mantine": "latest", "@blocknote/react": "latest", "@blocknote/shadcn": "latest", - "@mantine/core": "^8.3.11", - "@mantine/hooks": "^8.3.11", - "@mantine/utils": "^6.0.22", + "@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": "^4.7.0", - "vite": "^5.4.20" + "@vitejs/plugin-react": "^6.0.1", + "vite": "^8.0.8" } } \ No newline at end of file diff --git a/nx.json b/nx.json index 627efef773..c035fd35db 100644 --- a/nx.json +++ b/nx.json @@ -4,11 +4,10 @@ "release": { "projects": ["packages/*"], "version": { - "generatorOptions": { - "fallbackCurrentVersionResolver": "disk" - }, "preVersionCommand": "pnpm exec nx run-many -t build", - "conventionalCommits": true + "conventionalCommits": true, + "fallbackCurrentVersionResolver": "disk", + "preserveLocalDependencyProtocols": false }, "changelog": { "workspaceChangelog": { @@ -25,10 +24,17 @@ "build:site": { "cache": true, "dependsOn": ["^build"], - "inputs": ["{workspaceRoot}/examples/**/*", "{projectRoot}/**/*"], + "inputs": [ + "{workspaceRoot}/examples/**/*", + "{projectRoot}/**/*", + "!{projectRoot}/sqlite.db", + "!{projectRoot}/.env*", + "!{projectRoot}/validate-links.mjs", + "!{projectRoot}/coverage/**/*" + ], "outputs": [ - "{projectRoot}/dist", "{projectRoot}/.next", + "!{projectRoot}/.next/cache", "{projectRoot}/.source", "{projectRoot}/content/examples", "{projectRoot}/components/example/generated" @@ -36,7 +42,7 @@ }, "test": { "cache": true, - "dependsOn": ["^test"] + "dependsOn": ["build", "^build"] }, "lint": { "cache": true, @@ -44,7 +50,8 @@ }, "e2e": { "cache": true, - "dependsOn": ["^e2e"] + "dependsOn": ["build", "^build"] } - } + }, + "analytics": false } diff --git a/package.json b/package.json index b431e8ce3b..d0f2875eec 100644 --- a/package.json +++ b/package.json @@ -2,7 +2,7 @@ "name": "root", "type": "module", "devDependencies": { - "@nx/js": "^21.6.5", + "@nx/js": "22.6.5", "@typescript-eslint/eslint-plugin": "^5.62.0", "@typescript-eslint/parser": "^5.62.0", "concurrently": "9.1.2", @@ -10,16 +10,17 @@ "eslint-config-react-app": "^7.0.1", "eslint-plugin-import": "^2.32.0", "glob": "^10.5.0", - "nx": "^21.6.5", - "prettier": "^3.6.2", + "nx": "22.6.5", + "prettier": "3.6.2", "prettier-plugin-tailwindcss": "^0.6.14", - "serve": "14.2.4", + "serve": "14.2.6", "typescript": "^5.9.3", - "vitest": "^2.1.9", - "wait-on": "8.0.3" + "vitest": "^4.1.2", + "wait-on": "9.0.5" }, "pnpm": { "ignoredBuiltDependencies": [ + "canvas", "sharp", "workerd" ], @@ -28,12 +29,15 @@ "@sentry/cli", "@tailwindcss/oxide", "better-sqlite3", - "canvas", "esbuild", "msw", "nx", "unrs-resolver" - ] + ], + "overrides": { + "vitest": "4.1.2", + "@vitest/runner": "4.1.2" + } }, "packageManager": "pnpm@10.23.0+sha512.21c4e5698002ade97e4efe8b8b4a89a8de3c85a37919f957e7a0f30f38fbc5bbdd05980ffe29179b2fb6e6e691242e098d945d1601772cad0fef5fb6411e2a4b", "private": true, @@ -46,7 +50,7 @@ "clean": "nx run-many --target=clean", "deploy": "nx release --skip-publish", "gen": "nx run @blocknote/dev-scripts:gen", - "install-playwright": "cd tests && pnpx playwright install --with-deps", + "install-playwright": "cd tests && pnpm exec playwright install --with-deps", "e2e": "concurrently --success=first -r --kill-others \"pnpm run start -L\" \"wait-on http://localhost:3000 && cd tests && pnpm exec playwright test $PLAYWRIGHT_CONFIG\"", "e2e:updateSnaps": "concurrently --success=first -r --kill-others \"pnpm run start -L\" \"wait-on http://localhost:3000 && cd tests && pnpm run test:updateSnaps\"", "lint": "nx run-many --target=lint", @@ -56,5 +60,30 @@ "start": "serve playground/dist -c ../serve.json", "test": "nx run-many --target=test", "format": "prettier --write \"**/*.{js,jsx,ts,tsx,css,scss,md}\"" - } + }, + "overrides": { + "msw": "2.11.5", + "ai": "6.0.5", + "@ai-sdk/anthropic": "3.0.2", + "@ai-sdk/openai": "3.0.2", + "@ai-sdk/groq": "3.0.2", + "@ai-sdk/google": "3.0.2", + "@ai-sdk/mistral": "3.0.2", + "@ai-sdk/openai-compatible": "2.0.2", + "@ai-sdk/provider-utils": "4.0.2", + "@ai-sdk/react": "3.0.5", + "@ai-sdk/gateway": "3.0.4", + "@headlessui/react": "^2.2.4", + "@tiptap/core": "^3.0.0", + "@tiptap/pm": "^3.0.0" + }, + "workspaces": [ + "packages/*", + "examples/*/*", + "playground", + "fumadocs", + "docs", + "shared", + "tests" + ] } diff --git a/packages/ariakit/package.json b/packages/ariakit/package.json index f803ca01ed..b7291bf6ea 100644 --- a/packages/ariakit/package.json +++ b/packages/ariakit/package.json @@ -11,7 +11,7 @@ "directory": "packages/ariakit" }, "license": "MPL-2.0", - "version": "0.47.1", + "version": "0.51.0", "files": [ "dist", "types", @@ -57,22 +57,22 @@ }, "dependencies": { "@ariakit/react": "^0.4.19", - "@blocknote/core": "0.47.1", - "@blocknote/react": "0.47.1" + "@blocknote/core": "0.51.0", + "@blocknote/react": "0.51.0" }, "devDependencies": { "@types/react": "^19.2.3", "@types/react-dom": "^19.2.3", - "@vitejs/plugin-react": "^4.7.0", + "@vitejs/plugin-react": "^6.0.1", "eslint": "^8.57.1", - "react": "^19.2.3", - "react-dom": "^19.2.3", + "react": "^19.2.5", + "react-dom": "^19.2.5", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", + "vite": "^8.0.8", "vite-plugin-eslint": "^1.8.1", - "vite-plugin-externalize-deps": "^0.8.0" + "vite-plugin-externalize-deps": "^0.10.0" }, "peerDependencies": { "react": "^18.0 || ^19.0 || >= 19.0.0-rc", diff --git a/packages/ariakit/src/comments/Comment.tsx b/packages/ariakit/src/comments/Comment.tsx index efc1746f20..55ebfe2dba 100644 --- a/packages/ariakit/src/comments/Comment.tsx +++ b/packages/ariakit/src/comments/Comment.tsx @@ -58,7 +58,7 @@ export const Comment = forwardRef< actions, children, edited, - emojiPickerOpen, // Unused + emojiPickerOpen, ...rest } = props; @@ -72,7 +72,8 @@ export const Comment = forwardRef< (showActions === true || showActions === undefined || (showActions === "hover" && hovered) || - focused); + focused || + emojiPickerOpen); return ( ( + undefined, +); export const PopoverTrigger = forwardRef< HTMLButtonElement, @@ -27,6 +31,8 @@ export const PopoverContent = forwardRef< assertEmpty(rest); + const portalRoot = useContext(PortalRootContext); + return ( {children} @@ -44,7 +51,7 @@ export const PopoverContent = forwardRef< export const Popover = ( props: ComponentProps["Generic"]["Popover"]["Root"], ) => { - const { children, open, onOpenChange, position, ...rest } = props; + const { children, open, onOpenChange, position, portalRoot, ...rest } = props; assertEmpty(rest); @@ -54,7 +61,9 @@ export const Popover = ( setOpen={onOpenChange} placement={position} > - {children} + + {children} + ); }; diff --git a/packages/ariakit/src/style.css b/packages/ariakit/src/style.css index 1fde174b11..46917be46b 100644 --- a/packages/ariakit/src/style.css +++ b/packages/ariakit/src/style.css @@ -38,10 +38,6 @@ inset 0 1px 1px 1px var(--shadow); } -.bn-ak-popover { - z-index: 10000; -} - .bn-toolbar .bn-ak-popover { gap: 0.5rem; } diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx index 3bc2ac7bd0..c7e13350c8 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuItem.tsx @@ -22,7 +22,6 @@ export const SuggestionMenuItem = forwardRef< itemRef.current.closest(".bn-suggestion-menu, #ai-suggestion-menu")!, ); - if (overflow !== "none") { itemRef.current.scrollIntoView({ block: "nearest" }); } diff --git a/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx index 987142824b..cca2aeaf34 100644 --- a/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx +++ b/packages/ariakit/src/suggestionMenu/SuggestionMenuLoader.tsx @@ -19,7 +19,8 @@ export const SuggestionMenuLoader = forwardRef< height="1em" viewBox="0 -960 960 960" width="1em" - fill="#e8eaed"> + fill="#e8eaed" + >
diff --git a/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx b/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx index 0e164ecc05..81bbde8a62 100644 --- a/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx +++ b/packages/ariakit/src/suggestionMenu/gridSuggestionMenu/GridSuggestionMenuItem.tsx @@ -22,7 +22,6 @@ export const GridSuggestionMenuItem = forwardRef< itemRef.current.closest(".bn-grid-suggestion-menu")!, ); - if (overflow !== "none") { itemRef.current.scrollIntoView({ block: "nearest" }); } diff --git a/packages/ariakit/vite.config.ts b/packages/ariakit/vite.config.ts index 955be23212..76bbb3f7b4 100644 --- a/packages/ariakit/vite.config.ts +++ b/packages/ariakit/vite.config.ts @@ -16,7 +16,13 @@ export default defineConfig((conf) => ({ resolve: { alias: conf.command === "build" - ? ({} as Record) + ? ({ + // Vite 8's postcss-import can't resolve bare package specifiers in CSS @import + "@blocknote/react/style.css": path.resolve( + __dirname, + "../react/dist/style.css" + ), + } as Record) : ({ // load live from sources with live reload working "@blocknote/core": path.resolve(__dirname, "../core/src/"), @@ -30,6 +36,7 @@ export default defineConfig((conf) => ({ "blocknote-ariakit": path.resolve(__dirname, "src/index.tsx"), }, name: "blocknote-ariakit", + cssFileName: "style", formats: ["es", "cjs"], fileName: (format, entryName) => format === "es" ? `${entryName}.js` : `${entryName}.cjs`, @@ -43,7 +50,7 @@ export default defineConfig((conf) => ({ ...pkg.dependencies, ...((pkg as any).peerDependencies || {}), ...pkg.devDependencies, - }).includes(source) + }).some((dep) => source === dep || source.startsWith(dep + "/")) ) { return true; } @@ -64,7 +71,6 @@ export default defineConfig((conf) => ({ react: "React", "react-dom": "ReactDOM", }, - interop: "compat", // https://rollupjs.org/migration/#changed-defaults }, }, }, diff --git a/packages/code-block/package.json b/packages/code-block/package.json index 2ed200b77b..f69342f942 100644 --- a/packages/code-block/package.json +++ b/packages/code-block/package.json @@ -9,7 +9,7 @@ "directory": "packages/code-block" }, "license": "MPL-2.0", - "version": "0.47.1", + "version": "0.51.0", "files": [ "dist", "types", @@ -49,24 +49,23 @@ "test-watch": "vitest watch" }, "dependencies": { - "@blocknote/core": "0.47.1", - "@shikijs/core": "^3", - "@shikijs/engine-javascript": "^3", - "@shikijs/langs": "^3", - "@shikijs/langs-precompiled": "^3", - "@shikijs/themes": "^3", - "@shikijs/types": "^3" + "@blocknote/core": "0.51.0", + "@shikijs/core": "^4", + "@shikijs/engine-javascript": "^4", + "@shikijs/langs-precompiled": "^4", + "@shikijs/themes": "^4", + "@shikijs/types": "^4" }, "devDependencies": { "eslint": "^8.57.1", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", + "vite": "^8.0.8", "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.1.9" + "vitest": "^4.1.2" }, "peerDependencies": { - "@blocknote/core": "workspace:^" + "@blocknote/core": "0.51.0" }, "eslintConfig": { "extends": [ diff --git a/packages/code-block/src/shiki.bundle.ts b/packages/code-block/src/shiki.bundle.ts index 75962c807f..41363e1f00 100644 --- a/packages/code-block/src/shiki.bundle.ts +++ b/packages/code-block/src/shiki.bundle.ts @@ -4,7 +4,7 @@ import type { DynamicImportThemeRegistration, HighlighterGeneric, } from "@shikijs/types"; -import { createdBundledHighlighter } from "@shikijs/core"; +import { createBundledHighlighter } from "@shikijs/core"; import { createJavaScriptRegexEngine } from "@shikijs/engine-javascript"; type BundledLanguage = "typescript" | "ts" | "javascript" | "js" | "vue"; @@ -77,8 +77,7 @@ const bundledLanguages = { rust: () => import("@shikijs/langs-precompiled/rust"), rs: () => import("@shikijs/langs-precompiled/rust"), scala: () => import("@shikijs/langs-precompiled/scala"), - // Swift does not support pre-compilation right now - swift: () => import("@shikijs/langs/swift"), + swift: () => import("@shikijs/langs-precompiled/swift"), kotlin: () => import("@shikijs/langs-precompiled/kotlin"), kt: () => import("@shikijs/langs-precompiled/kotlin"), kts: () => import("@shikijs/langs-precompiled/kotlin"), @@ -91,7 +90,7 @@ const bundledThemes = { "github-light": () => import("@shikijs/themes/github-light"), } as Record; -const createHighlighter = /* @__PURE__ */ createdBundledHighlighter< +const createHighlighter = /* @__PURE__ */ createBundledHighlighter< BundledLanguage, BundledTheme >({ diff --git a/packages/code-block/vite.config.ts b/packages/code-block/vite.config.ts index 4eeea8794d..cb9f20516e 100644 --- a/packages/code-block/vite.config.ts +++ b/packages/code-block/vite.config.ts @@ -4,8 +4,6 @@ import { defineConfig } from "vite"; import pkg from "./package.json"; // import eslintPlugin from "vite-plugin-eslint"; - - // https://vitejs.dev/config/ export default defineConfig((conf) => ({ test: { @@ -43,7 +41,7 @@ export default defineConfig((conf) => ({ ...pkg.dependencies, ...((pkg as any).peerDependencies || {}), ...pkg.devDependencies, - }).includes(source) + }).some((dep) => source === dep || source.startsWith(dep + "/")) ) { return true; } @@ -61,7 +59,6 @@ export default defineConfig((conf) => ({ // Provide global variables to use in the UMD build // for externalized deps globals: {}, - interop: "compat", // https://rollupjs.org/migration/#changed-defaults }, }, }, diff --git a/packages/core/package.json b/packages/core/package.json index 6a2691eb22..c37562b259 100644 --- a/packages/core/package.json +++ b/packages/core/package.json @@ -11,7 +11,7 @@ "directory": "packages/core" }, "license": "MPL-2.0", - "version": "0.47.1", + "version": "0.51.0", "files": [ "dist", "types", @@ -86,70 +86,47 @@ "lint": "eslint src --max-warnings 0", "test": "vitest --run", "test-watch": "vitest watch", - "clean": "rimraf dist && rimraf types" + "clean": "rimraf dist && rimraf types", + "update-tlds": "node scripts/update-tlds.mjs" }, "dependencies": { "@emoji-mart/data": "^1.2.1", "@handlewithcare/prosemirror-inputrules": "^0.1.4", - "@shikijs/types": "^3", + "@shikijs/types": "^4", "@tanstack/store": "^0.7.7", "@tiptap/core": "^3.13.0", "@tiptap/extension-bold": "^3.13.0", "@tiptap/extension-code": "^3.13.0", - "@tiptap/extensions": "^3.13.0", "@tiptap/extension-horizontal-rule": "^3.13.0", "@tiptap/extension-italic": "^3.13.0", - "@tiptap/extension-link": "^3.13.0", "@tiptap/extension-paragraph": "^3.13.0", "@tiptap/extension-strike": "^3.13.0", "@tiptap/extension-text": "^3.13.0", "@tiptap/extension-underline": "^3.13.0", + "@tiptap/extensions": "^3.13.0", "@tiptap/pm": "^3.13.0", "emoji-mart": "^5.6.0", "fast-deep-equal": "^3.1.3", - "hast-util-from-dom": "^5.0.1", - "prosemirror-dropcursor": "^1.8.2", - "prosemirror-highlight": "^0.13.0", + "lib0": "^0.2.99", + "prosemirror-highlight": "^0.15.1", "prosemirror-model": "^1.25.4", "prosemirror-state": "^1.4.4", "prosemirror-tables": "^1.8.3", - "prosemirror-transform": "^1.10.5", + "prosemirror-transform": "^1.11.0", "prosemirror-view": "^1.41.4", - "rehype-format": "^5.0.1", - "rehype-parse": "^9.0.1", - "rehype-remark": "^10.0.1", - "rehype-stringify": "^10.0.1", - "remark-gfm": "^4.0.1", - "remark-parse": "^11.0.0", - "remark-rehype": "^11.1.2", - "remark-stringify": "^11.0.0", - "unified": "^11.0.5", - "unist-util-visit": "^5.0.0", - "uuid": "^8.3.2", "y-prosemirror": "^1.3.7", "y-protocols": "^1.0.6", "yjs": "^13.6.27" }, "devDependencies": { - "@types/emoji-mart": "^3.0.14", - "@types/hast": "^3.0.4", - "@types/uuid": "^8.3.4", "eslint": "^8.57.1", - "jsdom": "^25.0.1", + "jsdom": "^29.0.2", "rimraf": "^5.0.10", "rollup-plugin-webpack-stats": "^0.2.6", "typescript": "^5.9.3", - "vite": "^5.4.20", + "vite": "^8.0.8", "vite-plugin-eslint": "^1.8.1", - "vitest": "^2.1.9" - }, - "peerDependencies": { - "@hocuspocus/provider": "^2.15.2 || ^3.0.0" - }, - "peerDependenciesMeta": { - "@hocuspocus/provider": { - "optional": true - } + "vitest": "^4.1.2" }, "eslintConfig": { "extends": [ diff --git a/packages/core/scripts/update-tlds.mjs b/packages/core/scripts/update-tlds.mjs new file mode 100644 index 0000000000..43f4d02e15 --- /dev/null +++ b/packages/core/scripts/update-tlds.mjs @@ -0,0 +1,135 @@ +#!/usr/bin/env node +/** + * Regenerate src/extensions/tiptap-extensions/Link/helpers/tlds.ts from IANA's + * authoritative TLD list. + * + * Run with: pnpm --filter @blocknote/core update-tlds + * + * Encoding format ported from linkifyjs (MIT, https://github.com/nfrasser/linkifyjs): + * a sorted TLD list is built into a trie, then serialized as an ASCII string + * where letters descend the trie and digit runs mean "emit a word and pop N + * levels back up." Shared TLD prefixes (e.g. construction/consulting/ + * contractors) collapse, producing a payload smaller than a flat list. + * + * IDN punycode entries (XN--...) are skipped: the schemeless URL regex in + * linkDetector.ts requires ASCII-only TLDs, so unicode TLDs would never reach + * the validation step. + */ + +import { writeFileSync } from "node:fs"; +import { fileURLToPath } from "node:url"; +import { dirname, resolve } from "node:path"; + +const TLDS_URL = "https://data.iana.org/TLD/tlds-alpha-by-domain.txt"; + +const __dirname = dirname(fileURLToPath(import.meta.url)); +const OUT_PATH = resolve( + __dirname, + "../src/extensions/tiptap-extensions/Link/helpers/tlds.ts", +); + +function createTrie(words) { + const root = {}; + for (const word of words) { + let current = root; + for (const letter of word) { + if (!(letter in current)) { + current[letter] = {}; + } + current = current[letter]; + } + current.isWord = true; + } + return root; +} + +function encodeTrieHelper(trie) { + const output = []; + for (const k in trie) { + if (k === "isWord") { + output.push(0); + continue; + } + output.push(k); + output.push(...encodeTrieHelper(trie[k])); + if (typeof output[output.length - 1] === "number") { + output[output.length - 1] += 1; + } else { + output.push(1); + } + } + return output; +} + +function encodeTlds(tlds) { + return encodeTrieHelper(createTrie(tlds)).join(""); +} + +function decodeTlds(encoded) { + const words = []; + const stack = []; + let i = 0; + const digits = "0123456789"; + while (i < encoded.length) { + let popDigitCount = 0; + while (digits.indexOf(encoded[i + popDigitCount]) >= 0) { + popDigitCount++; + } + if (popDigitCount > 0) { + words.push(stack.join("")); + let popCount = parseInt(encoded.substring(i, i + popDigitCount), 10); + while (popCount-- > 0) { + stack.pop(); + } + i += popDigitCount; + } else { + stack.push(encoded[i]); + i++; + } + } + return words; +} + +async function main() { + console.log(`Fetching ${TLDS_URL}...`); + const response = await fetch(TLDS_URL); + if (!response.ok) { + throw new Error(`Failed to fetch IANA TLDs: ${response.status}`); + } + const body = await response.text(); + + const tlds = body + .split("\n") + .map((line) => line.trim()) + .filter((line) => line && !line.startsWith("#") && !/^XN--/i.test(line)) + .map((line) => line.toLowerCase()) + .sort(); + + console.log(`Encoding ${tlds.length} TLDs...`); + const encoded = encodeTlds(tlds); + + console.log("Round-trip asserting..."); + const decoded = decodeTlds(encoded); + if (JSON.stringify(decoded) !== JSON.stringify(tlds)) { + throw new Error("Encode/decode round-trip mismatch"); + } + + const fileContents = `// THIS FILE IS AUTO-GENERATED. DO NOT EDIT DIRECTLY. +// Source: ${TLDS_URL} +// Regenerate with: pnpm --filter @blocknote/core update-tlds +// Encoding format ported from linkifyjs (MIT) — trie collapsed into ASCII. + +export const ENCODED_TLDS = + "${encoded}"; +`; + + writeFileSync(OUT_PATH, fileContents); + console.log( + `Wrote ${OUT_PATH} (${encoded.length} chars, ${tlds.length} TLDs)`, + ); +} + +main().catch((err) => { + console.error(err); + process.exit(1); +}); diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap index dd575d1041..e854849d11 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/__snapshots__/insertBlocks.test.ts.snap @@ -664,7 +664,7 @@ exports[`Test insertBlocks > Insert multiple blocks after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1339,7 +1339,7 @@ exports[`Test insertBlocks > Insert multiple blocks before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1934,7 +1934,7 @@ exports[`Test insertBlocks > Insert single basic block after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2541,7 +2541,7 @@ exports[`Test insertBlocks > Insert single basic block before (without type) 2`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3136,7 +3136,7 @@ exports[`Test insertBlocks > Insert single basic block before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3847,7 +3847,7 @@ exports[`Test insertBlocks > Insert single complex block after 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4558,7 +4558,7 @@ exports[`Test insertBlocks > Insert single complex block before 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts index 81390947bf..25debee60c 100644 --- a/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/insertBlocks/insertBlocks.ts @@ -26,9 +26,11 @@ export function insertBlocks< const id = typeof referenceBlock === "string" ? referenceBlock : referenceBlock.id; const pmSchema = getPmSchema(tr); - const nodesToInsert = blocksToInsert.map((block) => - blockToNode(block, pmSchema), - ); + const nodesToInsert = blocksToInsert.map((block) => { + const node = blockToNode(block, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; + }); const posInfo = getNodeById(id, tr.doc); if (!posInfo) { diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap index 690c00017e..20c94c5ab8 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/__snapshots__/mergeBlocks.test.ts.snap @@ -540,7 +540,7 @@ exports[`Test mergeBlocks > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1091,7 +1091,7 @@ exports[`Test mergeBlocks > Blocks have different types 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1642,7 +1642,7 @@ exports[`Test mergeBlocks > First block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2192,7 +2192,7 @@ exports[`Test mergeBlocks > Second block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2749,7 +2749,7 @@ exports[`Test mergeBlocks > Second block is empty 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts index 57ec776d3a..654fbfdeba 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.test.ts @@ -2,7 +2,7 @@ import { describe, expect, it } from "vitest"; import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; -import { mergeBlocksCommand } from "./mergeBlocks.js"; +import { getParentBlockInfo, mergeBlocksCommand } from "./mergeBlocks.js"; const getEditor = setupTestEnv(); @@ -77,6 +77,20 @@ describe("Test mergeBlocks", () => { expect(anchorIsAtOldFirstBlockEndPos).toBeTruthy(); }); + it("getParentBlockInfo returns undefined for top-level block", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + const beforePos = getPosBeforeSelectedBlock(); + const doc = getEditor()._tiptapEditor.state.doc; + const $pos = doc.resolve(beforePos); + + expect($pos.depth - 1).toBeLessThan(1); + + const result = getParentBlockInfo(doc, beforePos); + + expect(result).toBeUndefined(); + }); + // We expect a no-op for each of the remaining tests as merging should only // happen for blocks which both have inline content. We also expect // `mergeBlocks` to return false as TipTap commands should do that instead of diff --git a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts index 2c397c153f..ce1a9455db 100644 --- a/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/mergeBlocks/mergeBlocks.ts @@ -10,9 +10,17 @@ import { * Returns the block info from the parent block * or undefined if we're at the root */ -export const getParentBlockInfo = (doc: Node, beforePos: number): BlockInfo | undefined => { +export const getParentBlockInfo = ( + doc: Node, + beforePos: number, +): BlockInfo | undefined => { const $pos = doc.resolve(beforePos); const depth = $pos.depth - 1; + + if (depth < 1) { + return undefined; + } + const parentBeforePos = $pos.before(depth); const parentNode = doc.resolve(parentBeforePos).nodeAfter; diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap index 902463bbc1..e59da045ed 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap @@ -557,7 +557,7 @@ exports[`Test moveBlocksDown > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -568,8 +568,25 @@ exports[`Test moveBlocksDown > Basic 1`] = ` ] `; -exports[`Test moveBlocksDown > Into children 1`] = ` +exports[`Test moveBlocksDown > Explicit block argument moves the given block 1`] = ` [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -589,23 +606,6 @@ exports[`Test moveBlocksDown > Into children 1`] = ` }, { "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 1", - "type": "text", - }, - ], - "id": "paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [ { @@ -1125,7 +1125,7 @@ exports[`Test moveBlocksDown > Into children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1136,7 +1136,7 @@ exports[`Test moveBlocksDown > Into children 1`] = ` ] `; -exports[`Test moveBlocksDown > Last block 1`] = ` +exports[`Test moveBlocksDown > Explicit block argument with nested block 1`] = ` [ { "children": [], @@ -1172,36 +1172,35 @@ exports[`Test moveBlocksDown > Last block 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [ { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 0", - "type": "text", - }, - ], - "id": "double-nested-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1213,11 +1212,11 @@ exports[`Test moveBlocksDown > Last block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1693,7 +1692,7 @@ exports[`Test moveBlocksDown > Last block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1704,7 +1703,7 @@ exports[`Test moveBlocksDown > Last block 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks 1`] = ` +exports[`Test moveBlocksDown > Into children 1`] = ` [ { "children": [], @@ -1724,41 +1723,24 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` "type": "paragraph", }, { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with props", - "type": "text", - }, - ], - "id": "paragraph-with-props", - "props": { - "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ + "children": [ { - "styles": {}, - "text": "Paragraph 1", - "type": "text", + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", }, - ], - "id": "paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [ { "children": [ { @@ -1827,6 +1809,23 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -2261,7 +2260,7 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2272,7 +2271,7 @@ exports[`Test moveBlocksDown > Multiple blocks 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] = ` +exports[`Test moveBlocksDown > Last block 1`] = ` [ { "children": [], @@ -2291,23 +2290,6 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 2", - "type": "text", - }, - ], - "id": "paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, { "children": [], "content": [ @@ -2378,6 +2360,23 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -2829,7 +2828,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2840,7 +2839,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] ] `; -exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks 1`] = ` [ { "children": [], @@ -2864,15 +2863,15 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, @@ -2951,15 +2950,15 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-2", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -3397,7 +3396,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3408,7 +3407,7 @@ exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` ] `; -exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks ending in block with children 1`] = ` [ { "children": [], @@ -3432,11 +3431,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3449,22 +3448,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-with-children", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3475,15 +3463,33 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo { "children": [ { - "children": [], + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": {}, - "text": "Double Nested Paragraph 0", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "double-nested-paragraph-0", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3495,11 +3501,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Paragraph with children", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3512,28 +3518,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with props", - "type": "text", - }, - ], - "id": "paragraph-with-props", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", "textAlignment": "center", @@ -3975,7 +3964,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo { "children": [], "content": [], - "id": "0", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3986,7 +3975,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested blo ] `; -exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks ending in nested block 1`] = ` [ { "children": [], @@ -4010,11 +3999,11 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph 2", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-2", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4027,15 +4016,15 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 1", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-1", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -4097,15 +4086,15 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 "content": [ { "styles": {}, - "text": "Paragraph 2", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-2", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, @@ -4543,7 +4532,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4554,7 +4543,7 @@ exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1 ] `; -exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks starting and ending in nested block 1`] = ` [ { "children": [], @@ -4609,18 +4598,12 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, { "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with props", - "type": "text", - }, - ], - "id": "paragraph-with-props", + "content": [], + "id": "paragraph-9", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -4676,6 +4659,23 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [], "content": [ @@ -5107,21 +5107,10 @@ exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` }, "type": "heading", }, - { - "children": [], - "content": [], - "id": "trailing-paragraph", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, ] `; -exports[`Test moveBlocksDown > Out of children 1`] = ` +exports[`Test moveBlocksDown > Multiple blocks starting in block with children 1`] = ` [ { "children": [], @@ -5157,6 +5146,23 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "type": "paragraph", }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, { "children": [ { @@ -5227,23 +5233,6 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "type": "paragraph", }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph with props", - "type": "text", - }, - ], - "id": "paragraph-with-props", - "props": { - "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", - }, - "type": "paragraph", - }, { "children": [], "content": [ @@ -5607,7 +5596,43 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` "type": "paragraph", }, { - "children": [], + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], "content": [ { "styles": { @@ -5640,33 +5665,31 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` "type": "heading", }, { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 1", - "type": "text", - }, - ], - "id": "double-nested-paragraph-1", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksDown > Multiple blocks starting in nested block 1`] = ` +[ + { + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 1", + "text": "Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-1", + "id": "paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5676,8 +5699,14 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, { "children": [], - "content": [], - "id": "trailing-paragraph", + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5685,21 +5714,16 @@ exports[`Test moveBlocksDown > Out of children 1`] = ` }, "type": "paragraph", }, -] -`; - -exports[`Test moveBlocksUp > Basic 1`] = ` -[ { "children": [], "content": [ { "styles": {}, - "text": "Paragraph 1", + "text": "Paragraph with children", "type": "text", }, ], - "id": "paragraph-1", + "id": "paragraph-with-children", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5712,48 +5736,30 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph 0", + "text": "Paragraph with props", "type": "text", }, ], - "id": "paragraph-0", + "id": "paragraph-with-props", "props": { "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "center", + "textColor": "red", }, "type": "paragraph", }, { "children": [ { - "children": [ - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Double Nested Paragraph 0", - "type": "text", - }, - ], - "id": "double-nested-paragraph-0", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", - }, - "type": "paragraph", - }, - ], + "children": [], "content": [ { "styles": {}, - "text": "Nested Paragraph 0", + "text": "Double Nested Paragraph 0", "type": "text", }, ], - "id": "nested-paragraph-0", + "id": "double-nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5765,11 +5771,11 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with children", + "text": "Nested Paragraph 0", "type": "text", }, ], - "id": "paragraph-with-children", + "id": "nested-paragraph-0", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5799,32 +5805,15 @@ exports[`Test moveBlocksUp > Basic 1`] = ` "content": [ { "styles": {}, - "text": "Paragraph with props", + "text": "Paragraph 3", "type": "text", }, ], - "id": "paragraph-with-props", + "id": "paragraph-3", "props": { "backgroundColor": "default", - "textAlignment": "center", - "textColor": "red", - }, - "type": "paragraph", - }, - { - "children": [], - "content": [ - { - "styles": {}, - "text": "Paragraph 3", - "type": "text", - }, - ], - "id": "paragraph-3", - "props": { - "backgroundColor": "default", - "textAlignment": "left", - "textColor": "default", + "textAlignment": "left", + "textColor": "default", }, "type": "paragraph", }, @@ -6245,7 +6234,2277 @@ exports[`Test moveBlocksUp > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksDown > Out of children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksUp > Basic 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksUp > Explicit block argument moves the given block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`Test moveBlocksUp > Explicit block argument with nested block 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 0", + "type": "text", + }, + ], + "id": "paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 1", + "type": "text", + }, + ], + "id": "paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 0", + "type": "text", + }, + ], + "id": "double-nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 0", + "type": "text", + }, + ], + "id": "nested-paragraph-0", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Paragraph with children", + "type": "text", + }, + ], + "id": "paragraph-with-children", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 2", + "type": "text", + }, + ], + "id": "paragraph-2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with props", + "type": "text", + }, + ], + "id": "paragraph-with-props", + "props": { + "backgroundColor": "default", + "textAlignment": "center", + "textColor": "red", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 3", + "type": "text", + }, + ], + "id": "paragraph-3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Paragraph", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "paragraph-with-styled-content", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 4", + "type": "text", + }, + ], + "id": "paragraph-4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "heading-0", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 5", + "type": "text", + }, + ], + "id": "paragraph-5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "image-0", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://via.placeholder.com/150", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 6", + "type": "text", + }, + ], + "id": "paragraph-6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 5", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 6", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 7", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 8", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 9", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "table-0", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 7", + "type": "text", + }, + ], + "id": "paragraph-7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [], + "id": "empty-paragraph", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph 8", + "type": "text", + }, + ], + "id": "paragraph-8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Double Nested Paragraph 1", + "type": "text", + }, + ], + "id": "double-nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested Paragraph 1", + "type": "text", + }, + ], + "id": "nested-paragraph-1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Heading", + "type": "text", + }, + { + "styles": {}, + "text": " with styled ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "content", + "type": "text", + }, + ], + "id": "heading-with-everything", + "props": { + "backgroundColor": "red", + "isToggleable": false, + "level": 2, + "textAlignment": "center", + "textColor": "red", + }, + "type": "heading", + }, + { + "children": [], + "content": [], + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6813,7 +9072,7 @@ exports[`Test moveBlocksUp > First block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7381,7 +9640,7 @@ exports[`Test moveBlocksUp > Into children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7949,7 +10208,7 @@ exports[`Test moveBlocksUp > Multiple blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8517,7 +10776,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in block with children 1`] = { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9085,7 +11344,7 @@ exports[`Test moveBlocksUp > Multiple blocks ending in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9652,7 +11911,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting and ending in nested block { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10220,7 +12479,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in block with children 1`] { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10787,7 +13046,7 @@ exports[`Test moveBlocksUp > Multiple blocks starting in nested block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11354,7 +13613,7 @@ exports[`Test moveBlocksUp > Out of children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts index 8c637d5985..763de289c5 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts @@ -2,7 +2,10 @@ import { NodeSelection, TextSelection } from "prosemirror-state"; import { CellSelection } from "prosemirror-tables"; import { describe, expect, it } from "vitest"; -import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; +import { + getBlockInfoFromTransaction, + getNearestBlockPos, +} from "../../../getBlockInfoFromPos.js"; import { setupTestEnv } from "../../setupTestEnv.js"; import { moveBlocksDown, @@ -204,6 +207,45 @@ describe("Test moveBlocksUp", () => { expect(getEditor().document).toMatchSnapshot(); }); + + it("Explicit block argument moves the given block", () => { + getEditor().setTextCursorPosition("paragraph-0"); + + moveBlocksUp(getEditor(), "paragraph-2"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Explicit block argument does not change the selection", () => { + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + moveBlocksUp(getEditor(), "paragraph-2"); + + const { anchor, head } = getEditor().transact((tr) => tr.selection); + const anchorBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, anchor).node.attrs.id, + ); + const headBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, head).node.attrs.id, + ); + expect(anchorBlockId).toBe("paragraph-1"); + expect(headBlockId).toBe("paragraph-1"); + }); + + it("Explicit block argument with first block is a no-op", () => { + const documentBefore = getEditor().document; + + moveBlocksUp(getEditor(), "paragraph-0"); + + expect(getEditor().document).toEqual(documentBefore); + }); + + it("Explicit block argument with nested block", () => { + moveBlocksUp(getEditor(), "nested-paragraph-1"); + + expect(getEditor().document).toMatchSnapshot(); + }); }); describe("Test moveBlocksDown", () => { @@ -232,7 +274,7 @@ describe("Test moveBlocksDown", () => { }); it("Last block", () => { - getEditor().setTextCursorPosition("trailing-paragraph"); + getEditor().setTextCursorPosition("paragraph-9"); moveBlocksDown(getEditor()); @@ -286,4 +328,43 @@ describe("Test moveBlocksDown", () => { expect(getEditor().document).toMatchSnapshot(); }); + + it("Explicit block argument moves the given block", () => { + getEditor().setTextCursorPosition("paragraph-9"); + + moveBlocksDown(getEditor(), "paragraph-0"); + + expect(getEditor().document).toMatchSnapshot(); + }); + + it("Explicit block argument does not change the selection", () => { + getEditor().setTextCursorPosition("paragraph-1"); + makeSelectionSpanContent("text"); + + moveBlocksDown(getEditor(), "paragraph-0"); + + const { anchor, head } = getEditor().transact((tr) => tr.selection); + const anchorBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, anchor).node.attrs.id, + ); + const headBlockId = getEditor().transact( + (tr) => getNearestBlockPos(tr.doc, head).node.attrs.id, + ); + expect(anchorBlockId).toBe("paragraph-1"); + expect(headBlockId).toBe("paragraph-1"); + }); + + it("Explicit block argument with last block is a no-op", () => { + const documentBefore = getEditor().document; + + moveBlocksDown(getEditor(), "trailing-paragraph"); + + expect(getEditor().document).toEqual(documentBefore); + }); + + it("Explicit block argument with nested block", () => { + moveBlocksDown(getEditor(), "nested-paragraph-0"); + + expect(getEditor().document).toMatchSnapshot(); + }); }); diff --git a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts index 8d4591123e..bb2f08dfca 100644 --- a/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts @@ -123,29 +123,50 @@ function updateBlockSelectionFromData( tr.setSelection(selection); } -/** - * Replaces any `columnList` blocks with the children of their columns. This is - * done here instead of in `getSelection` as we still need to remove the entire - * `columnList` node but only insert the `blockContainer` nodes inside it. - * @param blocks The blocks to flatten. - */ +// Replaces top-level `column` blocks with their children, as a `column` is not +// a valid block outside a `columnList`. Other blocks are returned as-is. function flattenColumns( blocks: Block[], ): Block[] { - return blocks - .map((block) => { - if (block.type === "columnList") { - return block.children - .map((column) => flattenColumns(column.children)) - .flat(); + return blocks.flatMap((block) => + block.type === "column" ? block.children : [block], + ); +} + +/** + * Removes the given blocks from the editor, then inserts them before/after a + * reference block. + * @param editor The BlockNote editor instance to move the blocks in. + * @param blocks The blocks to move. + * @param referenceBlock The reference block to insert the blocks before/after. + * @param placement Whether to insert the blocks before or after the reference + * block. + */ +export function moveBlocks( + editor: BlockNoteEditor, + blocks: Block[], + referenceBlock: BlockIdentifier, + placement: "before" | "after", +) { + editor.transact(() => { + // A `columnList` reference can be dissolved by `fixColumnList` when its + // `column`s are removed, leaving its ID invalid for re-insertion. Anchor + // to an adjacent block instead, which is unaffected by the removal. + const refBlock = editor.getBlock(referenceBlock); + if (refBlock?.type === "columnList") { + const adjacent = + placement === "after" + ? editor.getNextBlock(refBlock) + : editor.getPrevBlock(refBlock); + if (adjacent) { + referenceBlock = adjacent; + placement = placement === "after" ? "before" : "after"; } + } - return { - ...block, - children: flattenColumns(block.children), - }; - }) - .flat(); + editor.removeBlocks(blocks); + editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement); + }); } /** @@ -170,8 +191,7 @@ export function moveSelectedBlocksAndSelection( ]; const selectionData = getBlockSelectionData(editor); - editor.removeBlocks(blocks); - editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement); + moveBlocks(editor, blocks, referenceBlock, placement); updateBlockSelectionFromData(tr, selectionData); }); @@ -289,50 +309,91 @@ function getMoveDownPlacement( return { referenceBlock, placement }; } -export function moveBlocksUp(editor: BlockNoteEditor) { +export function moveBlocksUp( + editor: BlockNoteEditor, + blockIdentifier?: BlockIdentifier, +) { editor.transact(() => { - const selection = editor.getSelection(); - const block = selection?.blocks[0] || editor.getTextCursorPosition().block; + let sourceBlock: Block | undefined; + if (blockIdentifier) { + sourceBlock = editor.getBlock(blockIdentifier); + if (!sourceBlock) { + return; + } + } else { + const selection = editor.getSelection(); + sourceBlock = + selection?.blocks[0] || editor.getTextCursorPosition().block; + } const moveUpPlacement = getMoveUpPlacement( editor, - editor.getPrevBlock(block), - editor.getParentBlock(block), + editor.getPrevBlock(sourceBlock), + editor.getParentBlock(sourceBlock), ); if (!moveUpPlacement) { return; } - moveSelectedBlocksAndSelection( - editor, - moveUpPlacement.referenceBlock, - moveUpPlacement.placement, - ); + if (blockIdentifier) { + moveBlocks( + editor, + [sourceBlock], + moveUpPlacement.referenceBlock, + moveUpPlacement.placement, + ); + } else { + moveSelectedBlocksAndSelection( + editor, + moveUpPlacement.referenceBlock, + moveUpPlacement.placement, + ); + } }); } -export function moveBlocksDown(editor: BlockNoteEditor) { +export function moveBlocksDown( + editor: BlockNoteEditor, + blockIdentifier?: BlockIdentifier, +) { editor.transact(() => { - const selection = editor.getSelection(); - const block = - selection?.blocks[selection?.blocks.length - 1] || - editor.getTextCursorPosition().block; + let sourceBlock: Block | undefined; + if (blockIdentifier) { + sourceBlock = editor.getBlock(blockIdentifier); + if (!sourceBlock) { + return; + } + } else { + const selection = editor.getSelection(); + sourceBlock = + selection?.blocks[selection?.blocks.length - 1] || + editor.getTextCursorPosition().block; + } const moveDownPlacement = getMoveDownPlacement( editor, - editor.getNextBlock(block), - editor.getParentBlock(block), + editor.getNextBlock(sourceBlock), + editor.getParentBlock(sourceBlock), ); if (!moveDownPlacement) { return; } - moveSelectedBlocksAndSelection( - editor, - moveDownPlacement.referenceBlock, - moveDownPlacement.placement, - ); + if (blockIdentifier) { + moveBlocks( + editor, + [sourceBlock], + moveDownPlacement.referenceBlock, + moveDownPlacement.placement, + ); + } else { + moveSelectedBlocksAndSelection( + editor, + moveDownPlacement.referenceBlock, + moveDownPlacement.placement, + ); + } }); } diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap new file mode 100644 index 0000000000..95a5419dab --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/__snapshots__/nestBlock.test.ts.snap @@ -0,0 +1,952 @@ +// Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should handle unnesting the first of many siblings 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should move siblings after into lifted block's children 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-835: unnest block with siblings after and nested children > should not throw when unnesting a block that has siblings after it 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-844/847: unnest with complex nesting after parent operations > should handle sequential unnest operations 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-844/847: unnest with complex nesting after parent operations > should handle unnesting when block is only child 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child", + "type": "text", + }, + ], + "id": "child", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-899: Shift-Tab on second-level nested block > should not throw when unnesting a deeply nested block with siblings 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 2", + "type": "text", + }, + ], + "id": "grandchild2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Grandchild 1", + "type": "text", + }, + ], + "id": "grandchild1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 3", + "type": "text", + }, + ], + "id": "grandchild3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 4", + "type": "text", + }, + ], + "id": "grandchild4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "child2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-899: Shift-Tab on second-level nested block > should not throw when unnesting the last deeply nested block 1`] = ` +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 1", + "type": "text", + }, + ], + "id": "grandchild1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Grandchild 2", + "type": "text", + }, + ], + "id": "grandchild2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-953: unnest block with multi-level nested children > should preserve all deeply nested content when unnesting 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 5", + "type": "text", + }, + ], + "id": "block5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block A", + "type": "text", + }, + ], + "id": "blockA", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > BLO-953: unnest block with multi-level nested children > should preserve content when unnesting only child 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 4", + "type": "text", + }, + ], + "id": "block4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 3", + "type": "text", + }, + ], + "id": "block3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block A", + "type": "text", + }, + ], + "id": "blockA", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > Edge cases > should handle unnesting block with both existing children and siblings after 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Existing Grandchild", + "type": "text", + }, + ], + "id": "existing-grandchild", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "child2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 3", + "type": "text", + }, + ], + "id": "child3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > Edge cases > should handle unnesting with different block types 1`] = ` +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "parent", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph Sibling", + "type": "text", + }, + ], + "id": "para-sibling", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Heading Child", + "type": "text", + }, + ], + "id": "heading-child", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] +`; + +exports[`unnestBlock / liftListItem > nestBlock > should nest a block under its previous sibling 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; + +exports[`unnestBlock / liftListItem > nestBlock > should nest into a sibling that already has children (nestedBefore) 1`] = ` +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "child1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Block 2", + "type": "text", + }, + ], + "id": "block2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Block 1", + "type": "text", + }, + ], + "id": "block1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] +`; diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts new file mode 100644 index 0000000000..1938a3ea80 --- /dev/null +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.test.ts @@ -0,0 +1,661 @@ +import { describe, expect, it } from "vitest"; + +import { afterAll, beforeAll } from "vitest"; +import { PartialBlock } from "../../../../blocks/defaultBlocks.js"; +import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; + +/** + * Custom test setup with a document designed to reproduce nesting/unnesting bugs. + * + * BLO-835 / BLO-899: liftListItem produces invalid content when a nested block + * has siblings after it in the same blockGroup. + * BLO-953: Backspace at start of indented block with multi-level children + * causes deeply nested content to be lost. + * BLO-844 / BLO-847: Deleting parent block then operating on children causes + * RangeError. + */ + +function setupNestTestEnv() { + let editor: BlockNoteEditor; + const div = document.createElement("div"); + + beforeAll(() => { + editor = BlockNoteEditor.create(); + editor.mount(div); + }); + + afterAll(() => { + editor._tiptapEditor.destroy(); + editor = undefined as any; + }); + + return (doc: PartialBlock[]) => { + editor.replaceBlocks(editor.document, doc); + return editor; + }; +} + +const withEditor = setupNestTestEnv(); + +describe("unnestBlock / liftListItem", () => { + // BLO-835: liftListItem error with siblings after nested children + // Structure: + // block1 + // block2 ← unnest this + // block3 + // block4 + // block5 + // + // Expected: block2 lifts out, block5 becomes child of block2 + describe("BLO-835: unnest block with siblings after and nested children", () => { + it("should not throw when unnesting a block that has siblings after it", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should move siblings after into lifted block's children", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // block2 should now be at root level after block1 + // block5 should be a child of block2 + expect(editor.document).toMatchSnapshot(); + }); + + it("should handle unnesting the first of many siblings", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // block2 at root, block3 and block4 become children of block2 + expect(editor.document).toMatchSnapshot(); + }); + }); + + // BLO-899: Shift-Tab on second-level nested child (not last) causes error + // Structure: + // parent + // child1 + // grandchild1 ← unnest this + // grandchild2 + // child2 + // grandchild3 + // grandchild4 + // + describe("BLO-899: Shift-Tab on second-level nested block", () => { + it("should not throw when unnesting a deeply nested block with siblings", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "grandchild1", + type: "paragraph", + content: "Grandchild 1", + }, + { + id: "grandchild2", + type: "paragraph", + content: "Grandchild 2", + }, + ], + }, + { + id: "child2", + type: "paragraph", + content: "Child 2", + children: [ + { + id: "grandchild3", + type: "paragraph", + content: "Grandchild 3", + }, + { + id: "grandchild4", + type: "paragraph", + content: "Grandchild 4", + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("grandchild1", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // grandchild1 should become a sibling of child1 (at same level) + // grandchild2 should become a child of grandchild1 + expect(editor.document).toMatchSnapshot(); + }); + + it("should not throw when unnesting the last deeply nested block", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "grandchild1", + type: "paragraph", + content: "Grandchild 1", + }, + { + id: "grandchild2", + type: "paragraph", + content: "Grandchild 2", + }, + ], + }, + ], + }, + ]); + + // Unnesting the LAST child should always work (no siblings after) + editor.setTextCursorPosition("grandchild2", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // BLO-953: Backspace at start of indented block loses deeply nested content + // Structure: + // block1 + // blockA "text A" ← Backspace at start (unnest via keyboard) + // block3 + // block4 + // block5 + // + // Expected: blockA moves to root, all children preserved + describe("BLO-953: unnest block with multi-level nested children", () => { + it("should preserve all deeply nested content when unnesting", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "blockA", + type: "paragraph", + content: "Block A", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + children: [ + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + { + id: "block5", + type: "paragraph", + content: "Block 5", + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("blockA", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + const doc = editor.document; + + // All blocks should still exist in the document + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("block1"); + expect(allBlockIds).toContain("blockA"); + expect(allBlockIds).toContain("block3"); + expect(allBlockIds).toContain("block4"); + expect(allBlockIds).toContain("block5"); + + expect(doc).toMatchSnapshot(); + }); + + it("should preserve content when unnesting only child", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "blockA", + type: "paragraph", + content: "Block A", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + children: [ + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ], + }, + ], + }, + ]); + + editor.setTextCursorPosition("blockA", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + const doc = editor.document; + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("block1"); + expect(allBlockIds).toContain("blockA"); + expect(allBlockIds).toContain("block3"); + expect(allBlockIds).toContain("block4"); + + expect(doc).toMatchSnapshot(); + }); + }); + + // BLO-844 / BLO-847: Operations after deleting parent cause RangeError + // These bugs manifest when backspace merges/deletes a parent block and + // then further operations on the (now re-parented) children fail. + // + // The core issue is liftListItem failing when the children need to be + // reorganized. Testing the unnest operation directly. + describe("BLO-844/847: unnest with complex nesting after parent operations", () => { + it("should handle unnesting when block is only child", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child", + type: "paragraph", + content: "Child", + }, + ], + }, + ]); + + editor.setTextCursorPosition("child", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should handle sequential unnest operations", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "block2", + type: "paragraph", + content: "Block 2", + children: [ + { + id: "block3", + type: "paragraph", + content: "Block 3", + }, + ], + }, + { + id: "block4", + type: "paragraph", + content: "Block 4", + }, + ], + }, + ]); + + // First unnest block2 + editor.setTextCursorPosition("block2", "start"); + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // Then unnest block3 (which should now be child of block2) + editor.setTextCursorPosition("block3", "start"); + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // Additional edge cases + describe("Edge cases", () => { + it("should not unnest a root-level block", () => { + const editor = withEditor([ + { + id: "root-block", + type: "paragraph", + content: "Root Block", + }, + ]); + + editor.setTextCursorPosition("root-block", "start"); + + // Should be a no-op (can't unnest root level) + const canUnnest = editor.canUnnestBlock(); + expect(canUnnest).toBe(false); + }); + + it("should handle unnesting block with both existing children and siblings after", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + children: [ + { + id: "existing-grandchild", + type: "paragraph", + content: "Existing Grandchild", + }, + ], + }, + { + id: "child2", + type: "paragraph", + content: "Child 2", + }, + { + id: "child3", + type: "paragraph", + content: "Child 3", + }, + ], + }, + ]); + + editor.setTextCursorPosition("child1", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + // child1 should be at root level + // existing-grandchild should still be a child of child1 + // child2 and child3 should also become children of child1 + const doc = editor.document; + const allBlockIds = flattenBlockIds(doc); + expect(allBlockIds).toContain("parent"); + expect(allBlockIds).toContain("child1"); + expect(allBlockIds).toContain("existing-grandchild"); + expect(allBlockIds).toContain("child2"); + expect(allBlockIds).toContain("child3"); + + expect(doc).toMatchSnapshot(); + }); + + it("should handle unnesting with different block types", () => { + const editor = withEditor([ + { + id: "parent", + type: "paragraph", + content: "Parent", + children: [ + { + id: "heading-child", + type: "heading", + content: "Heading Child", + }, + { + id: "para-sibling", + type: "paragraph", + content: "Paragraph Sibling", + }, + ], + }, + ]); + + editor.setTextCursorPosition("heading-child", "start"); + + expect(() => { + editor.unnestBlock(); + }).not.toThrow(); + + expect(editor.document).toMatchSnapshot(); + }); + }); + + // nestBlock tests (sinkListItem) - ensuring nesting works correctly + describe("nestBlock", () => { + it("should nest a block under its previous sibling", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("should not nest the first block (no previous sibling)", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + ]); + + editor.setTextCursorPosition("block1", "start"); + + const canNest = editor.canNestBlock(); + expect(canNest).toBe(false); + }); + + it("should nest into a sibling that already has children (nestedBefore)", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + children: [ + { + id: "child1", + type: "paragraph", + content: "Child 1", + }, + ], + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + + expect(editor.document).toMatchSnapshot(); + }); + + it("nest then unnest should be a round trip", () => { + const editor = withEditor([ + { + id: "block1", + type: "paragraph", + content: "Block 1", + }, + { + id: "block2", + type: "paragraph", + content: "Block 2", + }, + ]); + + const originalDoc = JSON.parse(JSON.stringify(editor.document)); + + editor.setTextCursorPosition("block2", "start"); + editor.nestBlock(); + editor.unnestBlock(); + + // Content should be preserved (IDs may differ but structure/content same) + expect(editor.document.length).toBe(originalDoc.length); + expect(editor.document[0].content).toEqual(originalDoc[0].content); + expect(editor.document[1].content).toEqual(originalDoc[1].content); + }); + }); +}); + +/** Recursively collects all block IDs from a document */ +function flattenBlockIds(blocks: any[]): string[] { + const ids: string[] = []; + for (const block of blocks) { + if (block.id) { + ids.push(block.id); + } + if (block.children) { + ids.push(...flattenBlockIds(block.children)); + } + } + return ids; +} diff --git a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts index e8e97d77d3..c995faeda1 100644 --- a/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts @@ -1,17 +1,21 @@ -import { Fragment, NodeType, Slice } from "prosemirror-model"; +import { Fragment, NodeRange, NodeType, Slice } from "prosemirror-model"; import { Transaction } from "prosemirror-state"; -import { ReplaceAroundStep } from "prosemirror-transform"; +import { canJoin, liftTarget, ReplaceAroundStep } from "prosemirror-transform"; import { BlockNoteEditor } from "../../../../editor/BlockNoteEditor.js"; import { getBlockInfoFromTransaction } from "../../../getBlockInfoFromPos.js"; -// TODO: Unit tests /** - * This is a modified version of https://github.com/ProseMirror/prosemirror-schema-list/blob/569c2770cbb8092d8f11ea53ecf78cb7a4e8f15a/src/schema-list.ts#L232 + * Modified version of prosemirror-schema-list's sinkItem. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts * - * The original function derives too many information from the parentnode and itemtype + * Changes from the original: + * 1. Range predicate checks node.type instead of firstChild.type + * 2. nestedBefore checks groupType instead of parent.type + * 3. Slice creates groupType instead of parent.type + * 4. Operates on Transaction directly instead of state+dispatch */ -function sinkListItem( +function sinkItem( tr: Transaction, itemType: NodeType, groupType: NodeType, @@ -21,7 +25,7 @@ function sinkListItem( $to, (node) => node.childCount > 0 && - (node.type.name === "blockGroup" || node.type.name === "column"), // change necessary to not look at first item child type + (node.type.name === "blockGroup" || node.type.name === "column"), // change 1 ); if (!range) { return false; @@ -36,11 +40,11 @@ function sinkListItem( return false; } const nestedBefore = - nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change necessary to check groupType instead of parent.type + nodeBefore.lastChild && nodeBefore.lastChild.type === groupType; // change 2 const inner = Fragment.from(nestedBefore ? itemType.create() : null); const slice = new Slice( Fragment.from( - itemType.create(null, Fragment.from(groupType.create(null, inner))), // change necessary to create "groupType" instead of parent.type + itemType.create(null, Fragment.from(groupType.create(null, inner))), // change 3 ), nestedBefore ? 3 : 1, 0, @@ -66,7 +70,7 @@ function sinkListItem( export function nestBlock(editor: BlockNoteEditor) { return editor.transact((tr) => { - return sinkListItem( + return sinkItem( tr, editor.pmSchema.nodes["blockContainer"], editor.pmSchema.nodes["blockGroup"], @@ -74,8 +78,121 @@ export function nestBlock(editor: BlockNoteEditor) { }); } +/** + * Modified version of prosemirror-schema-list's liftToOuterList. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts + * + * Changes from the original: + * 1. Operates on Transaction directly instead of state+dispatch (TipTap compat) + * 2. When the lifted block already has children (a groupType child), uses deeper + * openStart/offset so siblings merge into the existing group instead of + * creating a second one (which would violate blockContainer's schema) + * 3. Uses groupType.create() instead of range.parent.copy() (same as sinkItem) + */ +function liftToOuterList( + tr: Transaction, + itemType: NodeType, + groupType: NodeType, // change 3 + range: NodeRange, +) { + const end = range.end; + const endOfList = range.$to.end(range.depth); + + if (end < endOfList) { + // There are siblings after the lifted items, which must become + // children of the last item + const blockBeingLifted = range.parent.child(range.endIndex - 1); + const nestedAfter = + blockBeingLifted.lastChild && + blockBeingLifted.lastChild.type === groupType; // change 2 + + tr.step( + new ReplaceAroundStep( + end - (nestedAfter ? 2 : 1), // change 2: go deeper when merging into existing children + endOfList, + end, + endOfList, + new Slice( + Fragment.from( + itemType.create(null, groupType.create()), // change 3 + ), + nestedAfter ? 2 : 1, // change 2: open deeper when merging into existing children + 0, + ), + nestedAfter ? 0 : 1, // change 2: Slice.insertAt offsets by openStart, so 0+2=2 lands inside existing bg + true, + ), + ); + range = new NodeRange( + tr.doc.resolve(range.$from.pos), + tr.doc.resolve(endOfList), + range.depth, + ); + } + + const target = liftTarget(range); + if (target == null) { + return false; + } + + tr.lift(range, target); + + const $after = tr.doc.resolve(tr.mapping.map(end, -1) - 1); + if ( + canJoin(tr.doc, $after.pos) && + $after.nodeBefore!.type === $after.nodeAfter!.type + ) { + tr.join($after.pos); + } + + tr.scrollIntoView(); + return true; +} + +/** + * Modified version of prosemirror-schema-list's liftListItem. + * https://github.com/ProseMirror/prosemirror-schema-list/blob/master/src/schema-list.ts + * + * Changes from the original: + * 1. Range predicate checks node.type instead of firstChild.type (same as sinkItem) + * 2. Passes groupType to liftToOuterList + * 3. Operates on Transaction directly instead of state+dispatch + * 4. Skips liftOutOfList (root-level blocks can't be unnested in BlockNote) + */ +export function liftItem( + tr: Transaction, + itemType: NodeType, + groupType: NodeType, // change 2 +) { + const { $from, $to } = tr.selection; + const range = $from.blockRange( + $to, + (node) => + node.childCount > 0 && + (node.type.name === "blockGroup" || node.type.name === "column"), // change 1 + ); + if (!range) { + return false; + } + + if ($from.node(range.depth - 1).type === itemType) { + // Inside a parent node + return liftToOuterList(tr, itemType, groupType, range); // change 2 + } + + // This is the "liftOutOfList" path — lifting out of a list entirely. + // Not applicable to BlockNote (root-level blocks can't be unnested). // change 4 + return false; +} + export function unnestBlock(editor: BlockNoteEditor) { - editor._tiptapEditor.commands.liftListItem("blockContainer"); + return editor.transact((tr) => + liftItem( + tr, + editor.pmSchema.nodes["blockContainer"], + editor.pmSchema.nodes["blockGroup"], + ), + ); } export function canNestBlock(editor: BlockNoteEditor) { diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap index d255acf235..d876b31175 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/__snapshots__/replaceBlocks.test.ts.snap @@ -470,7 +470,7 @@ exports[`Test replaceBlocks > Remove multiple consecutive blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -767,7 +767,7 @@ exports[`Test replaceBlocks > Remove multiple non-consecutive blocks 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1318,7 +1318,7 @@ exports[`Test replaceBlocks > Remove single block 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1850,7 +1850,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with multiple { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2342,7 +2342,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single ba { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2892,7 +2892,7 @@ exports[`Test replaceBlocks > Replace multiple consecutive blocks with single co { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3240,7 +3240,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with multi { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3548,7 +3548,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3914,7 +3914,7 @@ exports[`Test replaceBlocks > Replace multiple non-consecutive blocks with singl { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4516,7 +4516,7 @@ exports[`Test replaceBlocks > Replace single block with multiple 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5078,7 +5078,7 @@ exports[`Test replaceBlocks > Replace single block with single basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5698,7 +5698,7 @@ exports[`Test replaceBlocks > Replace single block with single complex 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts index 04a2425a33..f1e946f909 100644 --- a/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts +++ b/packages/core/src/api/blockManipulation/commands/replaceBlocks/replaceBlocks.ts @@ -27,9 +27,11 @@ export function removeAndInsertBlocks< const pmSchema = getPmSchema(tr); // Converts the `PartialBlock`s to ProseMirror nodes to insert them into the // document. - const nodesToInsert: Node[] = blocksToInsert.map((block) => - blockToNode(block, pmSchema), - ); + const nodesToInsert: Node[] = blocksToInsert.map((block) => { + const node = blockToNode(block, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; + }); const idsOfBlocksToRemove = new Set( blocksToRemove.map((block) => diff --git a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap index 60c3d1c1ed..8cd297eaee 100644 --- a/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/splitBlock/__snapshots__/splitBlock.test.ts.snap @@ -574,7 +574,7 @@ exports[`Test splitBlocks > Basic 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1159,7 +1159,7 @@ exports[`Test splitBlocks > Block has children 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1744,7 +1744,7 @@ exports[`Test splitBlocks > Don't keep props 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2329,7 +2329,7 @@ exports[`Test splitBlocks > Don't keep type 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2908,7 +2908,7 @@ exports[`Test splitBlocks > End of content 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3495,7 +3495,7 @@ exports[`Test splitBlocks > Keep type 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap index 3246168815..e4559884da 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/__snapshots__/updateBlock.test.ts.snap @@ -629,7 +629,7 @@ exports[`Test updateBlock > Revert all props 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1269,7 +1269,7 @@ exports[`Test updateBlock > Revert single prop 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -1909,7 +1909,7 @@ exports[`Test updateBlock > Update all props 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -2549,7 +2549,7 @@ exports[`Test updateBlock > Update children 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -3131,7 +3131,7 @@ exports[`Test updateBlock > Update inline content to no content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4055,7 +4055,7 @@ exports[`Test updateBlock > Update inline content to table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -4634,7 +4634,7 @@ exports[`Test updateBlock > Update no content to empty inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5217,7 +5217,7 @@ exports[`Test updateBlock > Update no content to empty table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -5808,7 +5808,7 @@ exports[`Test updateBlock > Update no content to inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -6735,7 +6735,7 @@ exports[`Test updateBlock > Update no content to table content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7303,7 +7303,7 @@ exports[`Test updateBlock > Update partial (offset start + end) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -7864,7 +7864,7 @@ exports[`Test updateBlock > Update partial (offset start) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8425,7 +8425,7 @@ exports[`Test updateBlock > Update partial (props + offset end) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -8993,7 +8993,7 @@ exports[`Test updateBlock > Update partial (table cell) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -9561,7 +9561,7 @@ exports[`Test updateBlock > Update partial (table row) 1`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10201,7 +10201,7 @@ exports[`Test updateBlock > Update single prop 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -10609,7 +10609,7 @@ exports[`Test updateBlock > Update table content to empty inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11029,7 +11029,7 @@ exports[`Test updateBlock > Update table content to inline content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -11443,7 +11443,7 @@ exports[`Test updateBlock > Update table content to no content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -12079,7 +12079,7 @@ exports[`Test updateBlock > Update type 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -12691,7 +12691,7 @@ exports[`Test updateBlock > Update with plain content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", @@ -13331,7 +13331,7 @@ exports[`Test updateBlock > Update with styled content 2`] = ` { "children": [], "content": [], - "id": "trailing-paragraph", + "id": "paragraph-9", "props": { "backgroundColor": "default", "textAlignment": "left", diff --git a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts index 4318b19ca7..a3e2b3b0db 100644 --- a/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts +++ b/packages/core/src/api/blockManipulation/commands/updateBlock/updateBlock.ts @@ -128,16 +128,18 @@ export function updateBlockTr< // for this, we do a nodeToBlock on the existing block to get the children. // it would be cleaner to use a ReplaceAroundStep, but this is a bit simpler and it's quite an edge case const existingBlock = nodeToBlock(blockInfo.bnBlock.node, pmSchema); + const replacementNode = blockToNode( + { + children: existingBlock.children, // if no children are passed in, use existing children + ...block, + }, + pmSchema, + ); + replacementNode.check(); // `blockToNode` is lenient; validate before mutating the doc tr.replaceWith( blockInfo.bnBlock.beforePos, blockInfo.bnBlock.afterPos, - blockToNode( - { - children: existingBlock.children, // if no children are passed in, use existing children - ...block, - }, - pmSchema, - ), + replacementNode, ); return; @@ -278,7 +280,9 @@ function updateChildren< const pmSchema = getPmSchema(tr); if (block.children !== undefined && block.children.length > 0) { const childNodes = block.children.map((child) => { - return blockToNode(child, pmSchema); + const node = blockToNode(child, pmSchema); + node.check(); // `blockToNode` is lenient; validate before mutating the doc + return node; }); // Checks if a blockGroup node already exists. diff --git a/packages/core/src/api/blockManipulation/setupTestEnv.ts b/packages/core/src/api/blockManipulation/setupTestEnv.ts index 37b4807063..bd1caf6300 100644 --- a/packages/core/src/api/blockManipulation/setupTestEnv.ts +++ b/packages/core/src/api/blockManipulation/setupTestEnv.ts @@ -185,7 +185,7 @@ const testDocument: PartialBlock[] = [ ], }, { - id: "trailing-paragraph", + id: "paragraph-9", type: "paragraph", }, ]; diff --git a/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts index b566dfdbe2..ffb298544f 100644 --- a/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts +++ b/packages/core/src/api/clipboard/fromClipboard/handleVSCodePaste.ts @@ -1,9 +1,6 @@ import { EditorView } from "prosemirror-view"; -export async function handleVSCodePaste( - event: ClipboardEvent, - view: EditorView, -) { +export function handleVSCodePaste(event: ClipboardEvent, view: EditorView) { const { schema } = view.state; if (!event.clipboardData) { @@ -17,8 +14,7 @@ export async function handleVSCodePaste( } if (!schema.nodes.codeBlock) { - view.pasteText(text); - return true; + return false; } const vscode = event.clipboardData!.getData("vscode-editor-data"); diff --git a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts index 415b3cb7be..9fa4ed3c55 100644 --- a/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts +++ b/packages/core/src/api/clipboard/fromClipboard/pasteExtension.ts @@ -57,8 +57,13 @@ function defaultPasteHandler({ } if (format === "vscode-editor-data") { - handleVSCodePaste(event, editor.prosemirrorView); - return true; + // If VSCode clipboard data cannot be parsed as a code block, try parsing + // `text/plain` as a fallback. + if (handleVSCodePaste(event, editor.prosemirrorView)) { + return true; + } + + format = "text/plain"; } if (format === "Files") { diff --git a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts index 3a6aeaffd5..e150af1309 100644 --- a/packages/core/src/api/clipboard/toClipboard/copyExtension.ts +++ b/packages/core/src/api/clipboard/toClipboard/copyExtension.ts @@ -140,16 +140,29 @@ export function selectedFragmentToHTML< editor, ); - const markdown = cleanHTMLToMarkdown(externalHTML); + // Code blocks are treated differently for copying: text/plain is the raw + // selected text instead of markdown. + const { $from, $to } = view.state.selection; + const parentBlockType = $from.parent.type.name; + const parentBlockSpec = editor.blockImplementations[parentBlockType as any]; + const isPurelyInsideCodeBlock = + $from.sameParent($to) && + parentBlockSpec?.implementation.meta?.code === true; + + const markdown = isPurelyInsideCodeBlock + ? view.state.doc.textBetween($from.pos, $to.pos) + : cleanHTMLToMarkdown(externalHTML); return { clipboardHTML, externalHTML, markdown }; } -const checkIfSelectionInNonEditableBlock = () => { - // Let browser handle event if selection is empty (nothing - // happens). - const selection = window.getSelection(); - if (!selection || selection.isCollapsed) { +const checkIfSelectionInNonEditableBlock = (view: EditorView) => { + // Use ProseMirror's internal selection state to check for empty selection. + // window.getSelection() returns null or a collapsed selection inside Shadow + // DOM (Firefox, Safari, and Chromium edge cases), causing this guard to + // misfire and silently skip clipboard writes. view.state.selection is always + // accurate regardless of DOM mode. + if (view.state.selection.empty) { return true; } @@ -158,16 +171,19 @@ const checkIfSelectionInNonEditableBlock = () => { // non-editable block. We only need to check one node as it's // not possible for the browser selection to start in an // editable block and end in a non-editable one. - let node = selection.focusNode; - while (node) { - if ( - node instanceof HTMLElement && - node.getAttribute("contenteditable") === "false" - ) { - return true; + const selection = window.getSelection(); + if (selection && !selection.isCollapsed) { + let node = selection.focusNode; + while (node) { + if ( + node instanceof HTMLElement && + node.getAttribute("contenteditable") === "false" + ) { + return true; + } + + node = node.parentElement; } - - node = node.parentElement; } return false; @@ -213,7 +229,7 @@ export const createCopyToClipboardExtension = < props: { handleDOMEvents: { copy(view, event) { - if (checkIfSelectionInNonEditableBlock()) { + if (checkIfSelectionInNonEditableBlock(view)) { return true; } @@ -222,7 +238,7 @@ export const createCopyToClipboardExtension = < return true; }, cut(view, event) { - if (checkIfSelectionInNonEditableBlock()) { + if (checkIfSelectionInNonEditableBlock(view)) { return true; } diff --git a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts index fb993f8309..72569ffced 100644 --- a/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts +++ b/packages/core/src/api/exporters/html/util/serializeBlocksExternalHTML.ts @@ -37,7 +37,7 @@ export function serializeInlineContentExternalHTML< editor: BlockNoteEditor, blockContent: PartialBlock["content"], serializer: DOMSerializer, - options?: { document?: Document }, + options?: { document?: Document; blockType?: string }, ) { let nodes: Node[]; @@ -45,9 +45,17 @@ export function serializeInlineContentExternalHTML< if (!blockContent) { throw new Error("blockContent is required"); } else if (typeof blockContent === "string") { - nodes = inlineContentToNodes([blockContent], editor.pmSchema); + nodes = inlineContentToNodes( + [blockContent], + editor.pmSchema, + options?.blockType, + ); } else if (Array.isArray(blockContent)) { - nodes = inlineContentToNodes(blockContent, editor.pmSchema); + nodes = inlineContentToNodes( + blockContent, + editor.pmSchema, + options?.blockType, + ); } else if (blockContent.type === "tableContent") { nodes = tableContentToNodes(blockContent, editor.pmSchema); } else { @@ -262,7 +270,7 @@ function serializeBlock< editor, block.content as any, // TODO serializer, - options, + { ...options, blockType: block.type }, ); ret.contentDOM.appendChild(ic); @@ -319,7 +327,13 @@ function serializeBlock< } } - if (editor.pmSchema.nodes[block.type as any].isInGroup("blockContent")) { + if ("childrenDOM" in ret && ret.childrenDOM) { + // block specifies where children should go (e.g. toggle blocks + // place children inside
) + ret.childrenDOM.append(childFragment); + } else if ( + editor.pmSchema.nodes[block.type as any].isInGroup("blockContent") + ) { // default "blockContainer" style blocks are flattened (no "nested block" support) for externalHTML, so append the child fragment to the outer fragment fragment.append(childFragment); } else { diff --git a/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts new file mode 100644 index 0000000000..7faa154dc6 --- /dev/null +++ b/packages/core/src/api/exporters/markdown/htmlToMarkdown.ts @@ -0,0 +1,813 @@ +/** + * Custom HTML-to-Markdown serializer for BlockNote. + * Replaces the unified/rehype-remark pipeline with a direct DOM-based implementation. + * + * Input: HTML string from createExternalHTMLExporter + * Output: GFM-compatible markdown string + */ + +/** + * Convert an HTML string (from BlockNote's external HTML exporter) to markdown. + */ +export function htmlToMarkdown(html: string): string { + // Use a temporary element to parse HTML. This works in both browser and + // server (JSDOM) environments, unlike `new DOMParser()` which may not be + // globally available in Node.js. + const container = document.createElement("div"); + container.innerHTML = html; + const result = serializeChildren(container, { + indent: "", + inListItem: false, + }); + return result.trim() + "\n"; +} + +interface SerializeContext { + indent: string; // current indentation prefix for list nesting + // True when the current node is being serialized as continuation content + // of a parent list item. Used to suppress trailing blank lines that would + // otherwise turn the parent list into a "loose" list. + inListItem: boolean; +} + +// ─── Main Serializer ───────────────────────────────────────────────────────── + +function serializeChildren(node: Node, ctx: SerializeContext): string { + let result = ""; + const children = Array.from(node.childNodes); + + for (let i = 0; i < children.length; i++) { + const child = children[i]; + result += serializeNode(child, ctx); + } + + return result; +} + +function serializeNode(node: Node, ctx: SerializeContext): string { + if (node.nodeType === 3 /* Node.TEXT_NODE */) { + return node.textContent || ""; + } + + if (node.nodeType !== 1 /* Node.ELEMENT_NODE */) { + return ""; + } + + const el = node as HTMLElement; + const tag = el.tagName.toLowerCase(); + + switch (tag) { + case "p": + return serializeParagraph(el, ctx); + case "h1": + case "h2": + case "h3": + case "h4": + case "h5": + case "h6": + return serializeHeading(el, ctx); + case "blockquote": + return serializeBlockquote(el, ctx); + case "pre": + return serializeCodeBlock(el, ctx); + case "ul": + return serializeUnorderedList(el, ctx); + case "ol": + return serializeOrderedList(el, ctx); + case "table": + return serializeTable(el, ctx); + case "hr": + return ctx.indent + "***\n\n"; + case "img": + return serializeImage(el, ctx); + case "video": + return serializeVideo(el, ctx); + case "audio": + return serializeAudio(el, ctx); + case "embed": + return serializeEmbed(el, ctx); + case "figure": + return serializeFigure(el, ctx); + case "a": + // Block-level link (file block) + return serializeBlockLink(el, ctx); + case "details": + return serializeDetails(el, ctx); + case "div": + // Page break or generic container — serialize children + return serializeChildren(el, ctx); + case "br": + return ""; + default: + return serializeChildren(el, ctx); + } +} + +// ─── Block Serializers ─────────────────────────────────────────────────────── + +function serializeParagraph(el: HTMLElement, ctx: SerializeContext): string { + const content = serializeInlineContent(el); + // Trim leading/trailing hard breaks (matching remark behavior) + const trimmed = trimHardBreaks(content); + if (ctx.inListItem) { + return trimmed; + } + return ctx.indent + trimmed + "\n\n"; +} + +function serializeHeading(el: HTMLElement, ctx: SerializeContext): string { + const level = parseInt(el.tagName[1], 10); + const prefix = "#".repeat(level) + " "; + const content = serializeInlineContent(el); + return ctx.indent + prefix + content + "\n\n"; +} + +function serializeBlockquote(el: HTMLElement, ctx: SerializeContext): string { + // Check if blockquote contains block-level elements (like

) + const blockChildren = Array.from(el.children).filter((child) => { + const tag = child.tagName.toLowerCase(); + return ["p", "ul", "ol", "pre", "blockquote", "table", "hr"].includes(tag); + }); + + let content: string; + if (blockChildren.length > 0) { + // Has block-level children — serialize each + const parts: string[] = []; + for (const child of blockChildren) { + const tag = child.tagName.toLowerCase(); + if (tag === "p") { + parts.push(serializeInlineContent(child as HTMLElement)); + } else { + const innerCtx: SerializeContext = { indent: "", inListItem: false }; + parts.push(serializeNode(child, innerCtx).trim()); + } + } + content = parts.join("\n\n"); + } else { + // No block-level children — treat entire content as inline + content = serializeInlineContent(el); + } + + const lines = content.split("\n"); + return lines.map((line) => ctx.indent + "> " + line).join("\n") + "\n\n"; +} + +function serializeCodeBlock(el: HTMLElement, ctx: SerializeContext): string { + const codeEl = el.querySelector("code"); + if (!codeEl) {return "";} + + const language = + codeEl.getAttribute("data-language") || + extractLanguageFromClass(codeEl.className) || + ""; + + // Extract code content, handling
elements as newlines + const code = extractCodeContent(codeEl); + + // Use a fence longer than the longest backtick run in the code + const longestRun = Math.max( + 0, + ...((code.match(/`+/g) ?? []).map((run) => run.length)) + ); + const fence = "`".repeat(Math.max(3, longestRun + 1)); + + // For empty code blocks, don't add a newline between the fences + if (!code) { + return ctx.indent + fence + language + "\n" + fence + "\n\n"; + } + + return ( + ctx.indent + + fence + + language + + "\n" + + code + + (code.endsWith("\n") ? "" : "\n") + + fence + + "\n\n" + ); +} + +function extractCodeContent(el: Element): string { + let result = ""; + for (const child of Array.from(el.childNodes)) { + if (child.nodeType === 3 /* Node.TEXT_NODE */) { + result += child.textContent || ""; + } else if (child.nodeType === 1 /* Node.ELEMENT_NODE */) { + const tag = (child as HTMLElement).tagName.toLowerCase(); + if (tag === "br") { + result += "\n"; + } else { + result += extractCodeContent(child as Element); + } + } + } + return result; +} + +function extractLanguageFromClass(className: string): string { + const match = className.match(/language-(\S+)/); + return match ? match[1] : ""; +} + +function serializeUnorderedList( + el: HTMLElement, + ctx: SerializeContext +): string { + let result = ""; + const items = Array.from(el.children).filter( + (child) => child.tagName.toLowerCase() === "li" + ); + + for (const item of items) { + result += serializeListItem(item as HTMLElement, "bullet", ctx); + } + + // Trailing blank line separates the list from the next block. Skip when + // this list is nested inside another list item — adding it would convert + // the parent list into a "loose" list (or break tightness). + if (!ctx.inListItem) { + result += "\n"; + } + return result; +} + +function serializeOrderedList(el: HTMLElement, ctx: SerializeContext): string { + let result = ""; + const items = Array.from(el.children).filter( + (child) => child.tagName.toLowerCase() === "li" + ); + const startNum = parseInt(el.getAttribute("start") || "1", 10); + + for (let i = 0; i < items.length; i++) { + const num = startNum + i; + result += serializeListItem(items[i] as HTMLElement, "ordered", ctx, num); + } + + if (!ctx.inListItem) { + result += "\n"; + } + return result; +} + +function serializeListItem( + el: HTMLElement, + listType: "bullet" | "ordered", + ctx: SerializeContext, + num?: number +): string { + // Check for checkbox (task list) - direct children only + let checkbox: HTMLInputElement | null = null; + let details: HTMLElement | null = null; + + for (const child of Array.from(el.children)) { + const tag = child.tagName.toLowerCase(); + if (tag === "input" && (child as HTMLInputElement).type === "checkbox") { + checkbox = child as HTMLInputElement; + } + if (tag === "details") { + details = child as HTMLElement; + } + } + + let marker: string; + let markerWidth: number; + + if (checkbox) { + const state = checkbox.checked ? "[x]" : "[ ]"; + marker = `* ${state} `; + // For child indentation, use bullet width (2), not full checkbox marker width + markerWidth = 2; + } else if (listType === "ordered") { + marker = `${num}. `; + markerWidth = marker.length; + } else { + marker = "* "; + markerWidth = 2; + } + + // Collect the item's inline content + let inlineContent: string; + let firstContentEl: Element | null; + + if (details) { + // Toggle item: get content from summary + const summary = details.querySelector("summary"); + const summaryP = summary?.querySelector("p"); + firstContentEl = details; + inlineContent = summaryP ? serializeInlineContent(summaryP) : ""; + } else { + firstContentEl = getFirstContentElement(el, checkbox); + inlineContent = firstContentEl ? serializeInlineContent(firstContentEl) : ""; + } + + // The marker line ends with a single `\n` so that consecutive list items + // produce a "tight" list (no blank line between markers). Continuation + // content within the item (nested lists, continuation paragraphs, other + // blocks) injects its own spacing as needed. + let result = ctx.indent + marker + inlineContent + "\n"; + + // Serialize child content (nested lists, continuation paragraphs, etc.) + const childIndent = ctx.indent + " ".repeat(markerWidth); + const childCtx: SerializeContext = { indent: childIndent, inListItem: true }; + + // For toggle items, also serialize children inside the details element + if (details) { + const summary = details.querySelector("summary"); + for (const child of Array.from(details.children)) { + if (child === summary) {continue;} + const childTag = child.tagName.toLowerCase(); + if (childTag === "p") { + const content = serializeInlineContent(child as HTMLElement); + // Continuation paragraph needs a blank line to separate it from the + // previous content; CommonMark would otherwise treat it as a soft + // wrap of that content. + result += "\n" + childIndent + content + "\n"; + } else { + result += serializeNode(child, childCtx); + } + } + } + + const children = Array.from(el.children); + for (const child of children) { + const childTag = child.tagName.toLowerCase(); + + // Skip the first content element and checkbox + if (child === firstContentEl || (child as HTMLElement) === checkbox) {continue;} + if (childTag === "input") {continue;} + + // Nested lists and other block content + if (childTag === "ul" || childTag === "ol") { + // Nested list flows directly under the parent marker — no blank line. + result += serializeNode(child, childCtx); + } else if (childTag === "p") { + // Continuation paragraph within list item — requires blank line before + // so it isn't read as part of the marker line's text. + const content = serializeInlineContent(child as HTMLElement); + result += "\n" + childIndent + content + "\n"; + } else { + // Other block-level children (code blocks, blockquotes, etc.) already + // emit their own separating newlines; prefix with a blank line so they + // are recognized as separate blocks. + result += "\n" + serializeNode(child, childCtx); + } + } + + return result; +} + +function getFirstContentElement( + li: HTMLElement, + checkbox: HTMLInputElement | null +): HTMLElement | null { + for (const child of Array.from(li.children)) { + if (child === checkbox) {continue;} + if (child.tagName.toLowerCase() === "input") {continue;} + const tag = child.tagName.toLowerCase(); + if (tag === "p" || tag === "span") {return child as HTMLElement;} + } + return null; +} + +// ─── Table Serializer ──────────────────────────────────────────────────────── + +function serializeTable(el: HTMLElement, ctx: SerializeContext): string { + // First, determine column count from colgroup or first row + const colgroup = el.querySelector("colgroup"); + let colCount = 0; + + if (colgroup) { + colCount = colgroup.querySelectorAll("col").length; + } + + const rows: string[][] = []; + let hasHeader = false; + + // Collect all rows, handling colspan/rowspan + const trElements = el.querySelectorAll("tr"); + // Build a grid to handle colspan/rowspan + const grid: (string | null)[][] = []; + + trElements.forEach((tr, rowIdx) => { + if (!grid[rowIdx]) {grid[rowIdx] = [];} + const cellElements = tr.querySelectorAll("th, td"); + let gridCol = 0; + + cellElements.forEach((cell) => { + // Find next empty column in this row + while (grid[rowIdx][gridCol] !== undefined) {gridCol++;} + + if (rowIdx === 0 && cell.tagName.toLowerCase() === "th") { + hasHeader = true; + } + + const content = escapeTableCell( + serializeInlineContent(cell as HTMLElement).trim() + ); + const colspan = parseInt(cell.getAttribute("colspan") || "1", 10); + const rowspan = parseInt(cell.getAttribute("rowspan") || "1", 10); + + // Fill the grid + for (let r = 0; r < rowspan; r++) { + for (let c = 0; c < colspan; c++) { + const ri = rowIdx + r; + if (!grid[ri]) {grid[ri] = [];} + grid[ri][gridCol + c] = r === 0 && c === 0 ? content : ""; + } + } + + gridCol += colspan; + }); + + // Update colCount + if (grid[rowIdx]) { + colCount = Math.max(colCount, grid[rowIdx].length); + } + }); + + // Convert grid to rows + for (const gridRow of grid) { + const row: string[] = []; + for (let c = 0; c < colCount; c++) { + row.push(gridRow && gridRow[c] !== undefined ? (gridRow[c] ?? "") : ""); + } + rows.push(row); + } + + if (rows.length === 0) {return "";} + + // Determine column widths + const colWidths: number[] = []; + for (let c = 0; c < colCount; c++) { + let maxWidth = 3; // minimum width for separator "---" + for (const row of rows) { + const cellWidth = c < row.length ? row[c].length : 0; + maxWidth = Math.max(maxWidth, cellWidth); + } + // Use minimum of 10 to match remark output + colWidths.push(Math.max(maxWidth, 10)); + } + + let result = ""; + + if (hasHeader) { + result += ctx.indent + formatTableRow(rows[0], colWidths, colCount) + "\n"; + result += ctx.indent + formatSeparatorRow(colWidths, colCount) + "\n"; + for (let r = 1; r < rows.length; r++) { + result += + ctx.indent + formatTableRow(rows[r], colWidths, colCount) + "\n"; + } + } else { + // No header — emit empty header + separator + const emptyRow = new Array(colCount).fill(""); + result += ctx.indent + formatTableRow(emptyRow, colWidths, colCount) + "\n"; + result += ctx.indent + formatSeparatorRow(colWidths, colCount) + "\n"; + for (const row of rows) { + result += + ctx.indent + formatTableRow(row, colWidths, colCount) + "\n"; + } + } + + result += "\n"; + return result; +} + +function escapeTableCell(text: string): string { + return text.replace(/\|/g, "\\|"); +} + +function formatTableRow( + cells: string[], + colWidths: number[], + colCount: number +): string { + const parts: string[] = []; + for (let c = 0; c < colCount; c++) { + const cell = c < cells.length ? cells[c] : ""; + parts.push(" " + cell.padEnd(colWidths[c]) + " "); + } + return "|" + parts.join("|") + "|"; +} + +function formatSeparatorRow(colWidths: number[], colCount: number): string { + const parts: string[] = []; + for (let c = 0; c < colCount; c++) { + parts.push(" " + "-".repeat(colWidths[c]) + " "); + } + return "|" + parts.join("|") + "|"; +} + +// ─── Media Serializers ─────────────────────────────────────────────────────── + +function serializeImage(el: HTMLElement, ctx: SerializeContext): string { + const src = el.getAttribute("src") || ""; + const alt = el.getAttribute("alt") || ""; + // Empty placeholder — preserve the block-level break, matching how + // serializeParagraph/serializeHeading emit `\n\n` for empty content. + if (!src) {return "\n\n";} + return ctx.indent + `![${alt}](${src})\n\n`; +} + +function serializeVideo(el: HTMLElement, ctx: SerializeContext): string { + const src = + el.getAttribute("src") || el.getAttribute("data-url") || ""; + const name = el.getAttribute("data-name") || el.getAttribute("title") || ""; + if (!src) {return "\n\n";} + return ctx.indent + `![${name}](${src})\n\n`; +} + +function serializeAudio(el: HTMLElement, ctx: SerializeContext): string { + const src = el.getAttribute("src") || ""; + if (!src) {return "\n\n";} + // Audio has no markdown syntax, so emit raw HTML. The markdown parser + // passes

${escapeHtmlText(captionText)}
` + : ""; + return ctx.indent + `
${tag}${captionPart}
\n\n`; +} + +function escapeHtmlAttr(value: string): string { + return value + .replace(/&/g, "&") + .replace(/"/g, """) + .replace(//g, ">"); +} + +function escapeHtmlText(value: string): string { + return value + .replace(/&/g, "&") + .replace(//g, ">"); +} + +function serializeBlockLink(el: HTMLElement, ctx: SerializeContext): string { + const href = el.getAttribute("href") || ""; + const text = el.textContent?.trim() || ""; + if (!href) {return ctx.indent + text + "\n\n";} + return ctx.indent + formatLink(text, href) + "\n\n"; +} + +/** + * Render a link, mirroring the remark-stringify behavior from + * TypeCellOS/BlockNote#2661: when the link label equals the URL (or is + * empty), emit the bare URL so that pasting the link into another input + * produces a valid href instead of ``-autolink brackets or redundant + * `[url](url)` markup. Otherwise emit `[text](url)` with the URL escaped so + * a `)` inside the URL does not prematurely close the destination. + */ +function formatLink(text: string, href: string): string { + if (!text || text === href) { + return href; + } + return `[${text}](${escapeLinkDestination(href)})`; +} + +function escapeLinkDestination(url: string): string { + return url.replace(/[\\()]/g, "\\$&"); +} + +function serializeDetails(el: HTMLElement, ctx: SerializeContext): string { + // Toggle heading or toggle list item + const summary = el.querySelector("summary"); + if (!summary) {return serializeChildren(el, ctx);} + + // Check if summary contains a heading + const heading = summary.querySelector("h1, h2, h3, h4, h5, h6"); + if (heading) { + let result = serializeHeading(heading as HTMLElement, ctx); + // Also serialize non-summary children of details + for (const child of Array.from(el.children)) { + if (child !== summary) { + result += serializeNode(child, ctx); + } + } + return result; + } + + // Otherwise serialize the summary content + return serializeChildren(summary, ctx); +} + +// ─── Inline Content Serializer ─────────────────────────────────────────────── + +function serializeInlineContent(el: Element): string { + let result = ""; + + for (const child of Array.from(el.childNodes)) { + if (child.nodeType === 3 /* Node.TEXT_NODE */) { + result += child.textContent || ""; + } else if (child.nodeType === 1 /* Node.ELEMENT_NODE */) { + const childEl = child as HTMLElement; + const tag = childEl.tagName.toLowerCase(); + + switch (tag) { + case "strong": + case "b": { + const inner = serializeInlineContent(childEl); + const { content, trailing } = extractTrailingWhitespace(inner); + if (content) { + result += `**${content}**${trailing}`; + } else { + // All whitespace — just output it without emphasis + result += trailing; + } + break; + } + case "em": + case "i": { + const inner = serializeInlineContent(childEl); + const { content, trailing } = extractTrailingWhitespace(inner); + if (content) { + result += `*${content}*${trailing}`; + } else { + result += trailing; + } + break; + } + case "s": + case "del": + result += `~~${serializeInlineContent(childEl)}~~`; + break; + case "code": { + const text = childEl.textContent || ""; + const longestRun = Math.max( + 0, + ...((text.match(/`+/g) ?? []).map((run) => run.length)) + ); + const fence = "`".repeat(longestRun + 1); + const needsPadding = + text.startsWith("`") || text.endsWith("`"); + result += fence + (needsPadding ? ` ${text} ` : text) + fence; + break; + } + case "u": + // No markdown equivalent — strip the tag, keep content + result += serializeInlineContent(childEl); + break; + case "a": { + const href = childEl.getAttribute("href") || ""; + const text = serializeInlineContent(childEl); + result += formatLink(text, href); + break; + } + case "br": + result += "\\\n"; + break; + case "span": + // Color spans, etc. — strip the tag, keep content + result += serializeInlineContent(childEl); + break; + case "img": { + const src = childEl.getAttribute("src") || ""; + const alt = childEl.getAttribute("alt") || ""; + result += `![${alt}](${src})`; + break; + } + case "video": { + const src = + childEl.getAttribute("src") || + childEl.getAttribute("data-url") || + ""; + const name = + childEl.getAttribute("data-name") || + childEl.getAttribute("title") || + ""; + result += `![${name}](${src})`; + break; + } + case "p": + // Paragraph inside inline context (e.g., table cell) + result += serializeInlineContent(childEl); + break; + case "input": + // Checkbox in task list — handled at block level + break; + default: + result += serializeInlineContent(childEl); + break; + } + } + } + + return result; +} + +/** + * Extract trailing whitespace from emphasis content. + * Moves trailing spaces outside the emphasis delimiters to produce valid markdown. + * E.g., `Bold ` → `**Bold** ` instead of `**Bold **`. + */ +function extractTrailingWhitespace(text: string): { + content: string; + trailing: string; +} { + const match = text.match(/^(.*?)(\s*)$/); + if (match) { + return { content: match[1], trailing: match[2] }; + } + return { content: text, trailing: "" }; +} + +/** + * Escape leading character after emphasis if it could break parsing. + * For example, "Heading" after "**Bold **" — the 'H' should be escaped + * if the trailing space was escaped. + */ + +/** + * Trim leading/trailing hard breaks from inline content. + * Matches remark behavior where
at start/end of paragraph is dropped. + */ +function trimHardBreaks(content: string): string { + // Remove leading hard breaks + let result = content.replace(/^(\\\n)+/, ""); + // Remove trailing hard breaks produced by `
` + result = result.replace(/(\\\n)+$/, ""); + return result; +} diff --git a/packages/core/src/api/exporters/markdown/markdownExporter.ts b/packages/core/src/api/exporters/markdown/markdownExporter.ts index 23aad8db7c..2f73616dc0 100644 --- a/packages/core/src/api/exporters/markdown/markdownExporter.ts +++ b/packages/core/src/api/exporters/markdown/markdownExporter.ts @@ -1,9 +1,4 @@ import { Schema } from "prosemirror-model"; -import rehypeParse from "rehype-parse"; -import rehypeRemark from "rehype-remark"; -import remarkGfm from "remark-gfm"; -import remarkStringify from "remark-stringify"; -import { unified } from "unified"; import { PartialBlock } from "../../../blocks/defaultBlocks.js"; import type { BlockNoteEditor } from "../../../editor/BlockNoteEditor.js"; @@ -13,25 +8,11 @@ import { StyleSchema, } from "../../../schema/index.js"; import { createExternalHTMLExporter } from "../html/externalHTMLExporter.js"; -import { removeUnderlines } from "./util/removeUnderlinesRehypePlugin.js"; -import { addSpacesToCheckboxes } from "./util/addSpacesToCheckboxesRehypePlugin.js"; -import { convertVideoToMarkdown } from "./util/convertVideoToMarkdownRehypePlugin.js"; +import { htmlToMarkdown } from "./htmlToMarkdown.js"; // Needs to be sync because it's used in drag handler event (SideMenuPlugin) export function cleanHTMLToMarkdown(cleanHTMLString: string) { - const markdownString = unified() - .use(rehypeParse, { fragment: true }) - .use(convertVideoToMarkdown) - .use(removeUnderlines) - .use(addSpacesToCheckboxes) - .use(rehypeRemark) - .use(remarkGfm) - .use(remarkStringify, { - handlers: { text: (node) => node.value }, - }) - .processSync(cleanHTMLString); - - return markdownString.value as string; + return htmlToMarkdown(cleanHTMLString); } export function blocksToMarkdown< diff --git a/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts deleted file mode 100644 index 7c03eb9a64..0000000000 --- a/packages/core/src/api/exporters/markdown/util/addSpacesToCheckboxesRehypePlugin.ts +++ /dev/null @@ -1,42 +0,0 @@ -import { Element as HASTElement, Parent as HASTParent } from "hast"; -import { fromDom } from "hast-util-from-dom"; - -/** - * Rehype plugin which adds a space after each checkbox input element. This is - * because remark doesn't add any spaces between the checkbox input and the text - * itself, but these are needed for correct Markdown syntax. - */ -export function addSpacesToCheckboxes() { - const helper = (tree: HASTParent) => { - if (tree.children && "length" in tree.children && tree.children.length) { - for (let i = tree.children.length - 1; i >= 0; i--) { - const child = tree.children[i]; - const nextChild = - i + 1 < tree.children.length ? tree.children[i + 1] : undefined; - - // Checks for paragraph element after checkbox input element. - if ( - child.type === "element" && - child.tagName === "input" && - child.properties?.type === "checkbox" && - nextChild?.type === "element" && - nextChild.tagName === "p" - ) { - // Converts paragraph to span, otherwise remark will think it needs to - // be on a new line. - nextChild.tagName = "span"; - // Adds a space after the checkbox input element. - nextChild.children.splice( - 0, - 0, - fromDom(document.createTextNode(" ")) as HASTElement, - ); - } else { - helper(child as HASTParent); - } - } - } - }; - - return helper; -} diff --git a/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts deleted file mode 100644 index a7de2e3442..0000000000 --- a/packages/core/src/api/exporters/markdown/util/convertVideoToMarkdownRehypePlugin.ts +++ /dev/null @@ -1,19 +0,0 @@ -import { Parent as HASTParent } from "hast"; -import { visit } from "unist-util-visit"; - -// Originally, rehypeParse parses videos as links, which is incorrect. -export function convertVideoToMarkdown() { - return (tree: HASTParent) => { - visit(tree, "element", (node, index, parent) => { - if (parent && node.tagName === "video") { - const src = node.properties?.src || node.properties?.["data-url"] || ""; - const name = - node.properties?.title || node.properties?.["data-name"] || ""; - parent.children[index!] = { - type: "text", - value: `![${name}](${src})`, - }; - } - }); - }; -} diff --git a/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts b/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts deleted file mode 100644 index 5b455d1b53..0000000000 --- a/packages/core/src/api/exporters/markdown/util/removeUnderlinesRehypePlugin.ts +++ /dev/null @@ -1,39 +0,0 @@ -import { Element as HASTElement, Parent as HASTParent } from "hast"; - -/** - * Rehype plugin which removes tags. Used to remove underlines before converting HTML to markdown, as Markdown - * doesn't support underlines. - */ -export function removeUnderlines() { - const removeUnderlinesHelper = (tree: HASTParent) => { - let numChildElements = tree.children.length; - - for (let i = 0; i < numChildElements; i++) { - const node = tree.children[i]; - - if (node.type === "element") { - // Recursively removes underlines from child elements. - removeUnderlinesHelper(node); - - if ((node as HASTElement).tagName === "u") { - // Lifts child nodes outside underline element, deletes the underline element, and updates current index & - // the number of child elements. - if (node.children.length > 0) { - tree.children.splice(i, 1, ...node.children); - - const numElementsAdded = node.children.length - 1; - numChildElements += numElementsAdded; - i += numElementsAdded; - } else { - tree.children.splice(i, 1); - - numChildElements--; - i--; - } - } - } - } - }; - - return removeUnderlinesHelper; -} diff --git a/packages/core/src/api/nodeConversions/blockToNode.ts b/packages/core/src/api/nodeConversions/blockToNode.ts index 206ff8d9fd..d970227a49 100644 --- a/packages/core/src/api/nodeConversions/blockToNode.ts +++ b/packages/core/src/api/nodeConversions/blockToNode.ts @@ -366,8 +366,9 @@ export function blockToNode( groupNode ? [contentNode, groupNode] : contentNode, ); } else if (schema.nodes[block.type].isInGroup("bnBlock")) { - // this is a bnBlock node like Column or ColumnList that directly translates to a prosemirror node - return schema.nodes[block.type].createChecked( + // `create` (not `createChecked`) so partial container blocks pass through; + // callers that mutate the doc validate via `node.check()` before inserting. + return schema.nodes[block.type].create( { id: id, ...block.props, diff --git a/packages/core/src/api/parsers/html/parseHTML.ts b/packages/core/src/api/parsers/html/parseHTML.ts index 43f3dc4559..16e03f883a 100644 --- a/packages/core/src/api/parsers/html/parseHTML.ts +++ b/packages/core/src/api/parsers/html/parseHTML.ts @@ -8,6 +8,7 @@ import { import { Block } from "../../../blocks/defaultBlocks.js"; import { nodeToBlock } from "../../nodeConversions/nodeToBlock.js"; import { nestedListsToBlockNoteStructure } from "./util/nestedLists.js"; +import { preprocessHTMLWhitespace } from "./util/normalizeWhitespace.js"; export function HTMLToBlocks< BSchema extends BlockSchema, @@ -15,6 +16,7 @@ export function HTMLToBlocks< S extends StyleSchema, >(html: string, pmSchema: Schema): Block[] { const htmlNode = nestedListsToBlockNoteStructure(html); + preprocessHTMLWhitespace(htmlNode); const parser = DOMParser.fromSchema(pmSchema); // Other approach might be to use diff --git a/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap b/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap index 68c0a1c817..1db488255b 100644 --- a/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap +++ b/packages/core/src/api/parsers/html/util/__snapshots__/nestedLists.test.ts.snap @@ -1,129 +1,144 @@ // Vitest Snapshot v1, https://vitest.dev/guide/snapshot.html exports[`Lift nested lists > Lifts multiple bullet lists 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
      -
    • Nested Bullet List Item 3
    • -
    • Nested Bullet List Item 4
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
      +
    • + Nested Bullet List Item 3 +
    • +
    • + Nested Bullet List Item 4 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts multiple bullet lists with content in between 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
    -
  • In between content
  • -
    -
      -
    • Nested Bullet List Item 3
    • -
    • Nested Bullet List Item 4
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
  • + In between content +
    • +
    • + Nested Bullet List Item 3 +
    • +
    • + Nested Bullet List Item 4 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists with content after nested list 1`] = ` -" -
    -
    -
  • Bullet List Item 1
  • -
    -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
    -
    -
  • More content in list item 1
  • -
  • Bullet List Item 2
  • -
-" +"
    +
  • + Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
  • + More content in list item 1 +
  • +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested bullet lists without li 1`] = ` -" -
    Bullet List Item 1 -
      -
    • Nested Bullet List Item 1
    • -
    • Nested Bullet List Item 2
    • -
    -
  • Bullet List Item 2
  • -
-" +"
    + Bullet List Item 1 +
      +
    • + Nested Bullet List Item 1 +
    • +
    • + Nested Bullet List Item 2 +
    • +
    +
  • + Bullet List Item 2 +
  • +
" `; exports[`Lift nested lists > Lifts nested mixed lists 1`] = ` -" -
    -
    -
  1. Numbered List Item 1
  2. -
    -
      -
    • Bullet List Item 1
    • -
    • Bullet List Item 2
    • -
    -
    -
    -
  3. Numbered List Item 2
  4. -
-" +"
    +
  1. + Numbered List Item 1 +
    • +
    • + Bullet List Item 1 +
    • +
    • + Bullet List Item 2 +
    • +
    +
  2. + Numbered List Item 2 +
  3. +
" `; exports[`Lift nested lists > Lifts nested numbered lists 1`] = ` -" -
    -
    -
  1. Numbered List Item 1
  2. -
    -
      -
    1. Nested Numbered List Item 1
    2. -
    3. Nested Numbered List Item 2
    4. -
    -
    -
    -
  3. Numbered List Item 2
  4. -
-" +"
    +
  1. + Numbered List Item 1 +
    1. +
    2. + Nested Numbered List Item 1 +
    3. +
    4. + Nested Numbered List Item 2 +
    5. +
    +
  2. + Numbered List Item 2 +
  3. +
" `; diff --git a/packages/core/src/api/parsers/html/util/nestedLists.test.ts b/packages/core/src/api/parsers/html/util/nestedLists.test.ts index 03fadebefe..e695efa9c4 100644 --- a/packages/core/src/api/parsers/html/util/nestedLists.test.ts +++ b/packages/core/src/api/parsers/html/util/nestedLists.test.ts @@ -1,20 +1,9 @@ import { describe, expect, it } from "vitest"; import { nestedListsToBlockNoteStructure } from "./nestedLists.js"; -import { unified } from "unified"; -import rehypeParse from "rehype-parse"; -import rehypeFormat from "rehype-format"; -import rehypeStringify from "rehype-stringify"; async function testHTML(html: string) { const htmlNode = nestedListsToBlockNoteStructure(html); - - const pretty = await unified() - .use(rehypeParse, { fragment: true }) - .use(rehypeFormat) - .use(rehypeStringify) - .process(htmlNode.innerHTML); - - expect(pretty.value).toMatchSnapshot(); + expect(htmlNode.innerHTML).toMatchSnapshot(); } describe("Lift nested lists", () => { diff --git a/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts b/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts new file mode 100644 index 0000000000..9cacd86e15 --- /dev/null +++ b/packages/core/src/api/parsers/html/util/normalizeWhitespace.ts @@ -0,0 +1,87 @@ +/** + * Checks if the given HTML element contains markers indicating it was + * generated by Notion. Notion uses `\n` in text nodes to represent hard + * breaks, which is non-standard but intentional. + * + * Detected by the `` comment that Notion places + * on the clipboard. + */ +function isNotionHTML(element: HTMLElement): boolean { + const walker = element.ownerDocument.createTreeWalker( + element, + // NodeFilter.SHOW_COMMENT + 128, + ); + + let node: Node | null; + while ((node = walker.nextNode())) { + if (/^\s*notionvc:/.test(node.nodeValue || "")) { + return true; + } + } + + return false; +} + +/** + * Normalizes whitespace in text nodes by collapsing runs of whitespace + * (including newlines) to single spaces, matching CSS white-space:normal + * behavior. + * + * This is needed because ProseMirror's DOMParser, when `linebreakReplacement` + * is set in the schema (as BlockNote does for hard breaks), converts `\n` + * characters in text nodes to hard break nodes instead of collapsing them. + * This causes HTML source line wrapping (e.g. from MS Word) to create + * visible line breaks in the editor. + * + * Skipped for sources like Notion that intentionally use `\n` in text nodes + * to represent hard breaks instead of `
` tags. + * + * Skips `
` and `` elements where whitespace should be preserved.
+ */
+function normalizeTextNodeWhitespace(element: HTMLElement) {
+  const preserveWSTags = new Set(["PRE", "CODE"]);
+  const walker = element.ownerDocument.createTreeWalker(
+    element,
+    // NodeFilter.SHOW_TEXT
+    4,
+    {
+      acceptNode(node) {
+        // Skip text nodes inside pre/code elements
+        let parent = node.parentElement;
+        while (parent && parent !== element) {
+          if (preserveWSTags.has(parent.tagName)) {
+            // NodeFilter.FILTER_REJECT
+            return 2;
+          }
+          parent = parent.parentElement;
+        }
+        // NodeFilter.FILTER_ACCEPT
+        return 1;
+      },
+    },
+  );
+
+  const textNodes: Text[] = [];
+  let node: Node | null;
+  while ((node = walker.nextNode())) {
+    textNodes.push(node as Text);
+  }
+
+  for (const textNode of textNodes) {
+    if (textNode.nodeValue && /[\r\n]/.test(textNode.nodeValue)) {
+      textNode.nodeValue = textNode.nodeValue.replace(/[ \t\r\n\f]+/g, " ");
+    }
+  }
+}
+
+/**
+ * Normalizes whitespace in HTML text nodes to match standard CSS
+ * white-space:normal behavior. Skipped for Notion HTML which intentionally
+ * uses `\n` for hard breaks.
+ */
+export function preprocessHTMLWhitespace(element: HTMLElement) {
+  if (!isNotionHTML(element)) {
+    normalizeTextNodeWhitespace(element);
+  }
+}
diff --git a/packages/core/src/api/parsers/markdown/markdownToHtml.ts b/packages/core/src/api/parsers/markdown/markdownToHtml.ts
new file mode 100644
index 0000000000..6cf94bf4b8
--- /dev/null
+++ b/packages/core/src/api/parsers/markdown/markdownToHtml.ts
@@ -0,0 +1,1194 @@
+import { isVideoUrl } from "../../../util/string.js";
+
+/**
+ * Custom markdown-to-HTML converter for BlockNote.
+ * Replaces the unified/remark/rehype pipeline with a direct, minimal implementation
+ * that handles exactly the markdown features BlockNote needs.
+ */
+
+// ─── HTML Escaping ───────────────────────────────────────────────────────────
+
+function escapeHtml(str: string): string {
+  return str
+    .replace(/&/g, "&")
+    .replace(//g, ">")
+    .replace(/"/g, """);
+}
+
+// ─── Helpers ─────────────────────────────────────────────────────────────────
+
+function isAlphanumeric(char: string | undefined): boolean {
+  if (!char) {
+    return false;
+  }
+  return /\w/.test(char);
+}
+
+/**
+ * Returns true when an underscore delimiter at position `i` is "intraword",
+ * meaning the characters on both sides are alphanumeric (e.g. `snake_case`).
+ * In that case the underscore should NOT be treated as emphasis per CommonMark.
+ */
+function isIntraword(text: string, i: number, delimLen: number): boolean {
+  const before = i > 0 ? text[i - 1] : undefined;
+  const after =
+    i + delimLen < text.length ? text[i + delimLen] : undefined;
+  return isAlphanumeric(before) && isAlphanumeric(after);
+}
+
+// ─── Inline Parser ───────────────────────────────────────────────────────────
+
+type InlineTokenizer = (
+  text: string,
+  i: number
+) => { html: string; end: number } | null;
+
+function tryBackslashEscape(
+  text: string,
+  i: number
+): { html: string; end: number } | null {
+  if (text[i] !== "\\" || i + 1 >= text.length) {return null;}
+  const next = text[i + 1];
+  // Hard line break: backslash at end of line
+  if (next === "\n") {
+    return { html: "
\n", end: i + 2 }; + } + // Escapable characters + if ("\\`*_{}[]()#+-.!~|>".includes(next)) { + return { html: escapeHtml(next), end: i + 2 }; + } + return null; +} + +function tryInlineCode( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] !== "`") {return null;} + return parseInlineCode(text, i); +} + +function tryImage( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] !== "!" || text[i + 1] !== "[") {return null;} + return parseImage(text, i); +} + +function tryLink( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] !== "[") {return null;} + return parseLink(text, i); +} + +function tryStrikethrough( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] !== "~" || text[i + 1] !== "~") {return null;} + return parseDelimited(text, i, "~~", "", ""); +} + +function tryBoldItalic( + text: string, + i: number +): { html: string; end: number } | null { + if ( + (text[i] === "*" && text[i + 1] === "*" && text[i + 2] === "*") || + (text[i] === "_" && + text[i + 1] === "_" && + text[i + 2] === "_" && + !isIntraword(text, i, 3)) + ) { + const delimiter = text.substring(i, i + 3); + return parseDelimited(text, i, delimiter, "", ""); + } + return null; +} + +function tryBold( + text: string, + i: number +): { html: string; end: number } | null { + if ( + (text[i] === "*" && text[i + 1] === "*") || + (text[i] === "_" && text[i + 1] === "_" && !isIntraword(text, i, 2)) + ) { + const delimiter = text.substring(i, i + 2); + return parseDelimited(text, i, delimiter, "", ""); + } + return null; +} + +function tryItalic( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] === "*" || (text[i] === "_" && !isIntraword(text, i, 1))) { + return parseDelimited(text, i, text[i], "", ""); + } + return null; +} + +function trySoftBreak( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] === "\n") { + return { html: "
\n", end: i + 1 }; + } + return null; +} + +// Inline raw HTML: pass through tags, comments, CDATA, processing +// instructions, and declarations verbatim so authors can mix HTML into +// markdown (e.g. `text foo more`). Anything that doesn't match +// these shapes falls through and gets HTML-escaped as plain text. +const INLINE_HTML_TAG_RE = + /^<\/?[a-zA-Z][a-zA-Z0-9-]*(?:\s+[a-zA-Z_:][a-zA-Z0-9_.:-]*(?:\s*=\s*(?:"[^"]*"|'[^']*'|[^\s"'=<>`]+))?)*\s*\/?>/; +const HTML_COMMENT_RE = /^/; +const HTML_CDATA_RE = /^/; +const HTML_PI_RE = /^<\?[\s\S]*?\?>/; +const HTML_DECL_RE = /^/; + +function tryInlineHtml( + text: string, + i: number +): { html: string; end: number } | null { + if (text[i] !== "<") {return null;} + const rest = text.substring(i); + for (const re of [ + HTML_COMMENT_RE, + HTML_CDATA_RE, + HTML_PI_RE, + HTML_DECL_RE, + INLINE_HTML_TAG_RE, + ]) { + const m = rest.match(re); + if (m) { + return { html: m[0], end: i + m[0].length }; + } + } + return null; +} + +/** Characters that can start an inline syntax token. */ +const SPECIAL_CHARS = new Set("\\`![~*_\n<"); + +/** + * Ordered array of inline tokenizers, tried in priority order. + * The first match wins. + */ +const inlineTokenizers: InlineTokenizer[] = [ + tryBackslashEscape, + tryInlineCode, + tryImage, + tryLink, + tryStrikethrough, + tryBoldItalic, // *** / ___ + tryBold, // ** / __ + tryItalic, // * / _ + tryInlineHtml, + trySoftBreak, +]; + +/** + * Parse inline markdown syntax and return HTML. + * Handles: bold, italic, bold+italic, strikethrough, inline code, + * links, images (with video detection), hard line breaks, backslash escapes. + */ +function parseInline(text: string): string { + let result = ""; + let i = 0; + + while (i < text.length) { + // Hard line break: 2+ trailing spaces immediately before a newline. + // (The other hard-break form, backslash + newline, is handled by + // tryBackslashEscape.) Strip the trailing spaces from the accumulated + // result before emitting the
. + if ( + text[i] === "\n" && + i >= 2 && + text[i - 1] === " " && + text[i - 2] === " " + ) { + result = result.replace(/ +$/, ""); + result += "
\n"; + i++; + continue; + } + + // Try each tokenizer in priority order + let matched = false; + if (SPECIAL_CHARS.has(text[i])) { + for (const tokenizer of inlineTokenizers) { + const r = tokenizer(text, i); + if (r) { + result += r.html; + i = r.end; + matched = true; + break; + } + } + } + + if (!matched) { + // Batch consecutive plain-text characters and escape once + const runStart = i; + i++; + while (i < text.length && !SPECIAL_CHARS.has(text[i])) { + i++; + } + result += escapeHtml(text.substring(runStart, i)); + } + } + + return result; +} + +function parseInlineCode( + text: string, + start: number +): { html: string; end: number } | null { + // Count opening backticks + let openCount = 0; + let i = start; + while (i < text.length && text[i] === "`") { + openCount++; + i++; + } + + // Find matching closing backticks + let j = i; + while (j < text.length) { + if (text[j] === "`") { + let closeCount = 0; + const closeStart = j; + while (j < text.length && text[j] === "`") { + closeCount++; + j++; + } + if (closeCount === openCount) { + let code = text.substring(i, closeStart); + // Per CommonMark: line endings inside a code span are converted to + // single spaces, then if the result starts AND ends with a space and + // is not all-spaces, one leading + trailing space is stripped (so + // `` ` `foo` ` `` is ``foo``). + code = code.replace(/\n/g, " "); + if ( + code.length >= 2 && + code[0] === " " && + code[code.length - 1] === " " && + /[^ ]/.test(code) + ) { + code = code.substring(1, code.length - 1); + } + return { + html: `${escapeHtml(code)}`, + end: j, + }; + } + } else { + j++; + } + } + return null; +} + +function parseImage( + text: string, + start: number +): { html: string; end: number } | null { + // ![alt](url) or ![alt](url "title") + // Use balanced bracket matching to handle nested/escaped brackets in alt text + const altEnd = findClosingBracket(text, start + 1); + if (altEnd === -1) {return null;} + const altStart = start + 2; // after ![ + + if (text[altEnd + 1] !== "(") {return null;} + + const urlStart = altEnd + 2; + const parenEnd = findClosingParen(text, urlStart - 1); + if (parenEnd === -1) {return null;} + + const alt = text.substring(altStart, altEnd); + const { url, title } = parseDestinationAndTitle( + text.substring(urlStart, parenEnd), + ); + + if (isVideoUrl(url)) { + // Use the alt text as the video's display name (falling back to the + // title) so a video link written with the standard `![name](url)` form + // round-trips into BlockNote's video block. Captioned videos go through + // raw `
` HTML instead, see htmlToMarkdown.serializeMediaFigure. + const name = alt || title; + return { + html: ``, + end: parenEnd + 1, + }; + } + + const titleAttr = + title !== undefined ? ` title="${escapeHtml(title)}"` : ""; + return { + html: `${escapeHtml(alt)}`, + end: parenEnd + 1, + }; +} + +function parseLink( + text: string, + start: number +): { html: string; end: number } | null { + // [text](url) + const textStart = start + 1; + const textEnd = findClosingBracket(text, start); + if (textEnd === -1) {return null;} + + if (text[textEnd + 1] !== "(") {return null;} + + const urlStart = textEnd + 2; + const parenEnd = findClosingParen(text, textEnd + 1); + if (parenEnd === -1) {return null;} + + const linkText = text.substring(textStart, textEnd); + const { url, title } = parseDestinationAndTitle( + text.substring(urlStart, parenEnd), + ); + + const titleAttr = + title !== undefined ? ` title="${escapeHtml(title)}"` : ""; + return { + html: `${parseInline(linkText)}`, + end: parenEnd + 1, + }; +} + +function findClosingBracket(text: string, openPos: number): number { + let depth = 0; + for (let i = openPos; i < text.length; i++) { + if (text[i] === "\\" && i + 1 < text.length) { + i++; // skip escaped + continue; + } + if (text[i] === "[") {depth++;} + if (text[i] === "]") { + depth--; + if (depth === 0) {return i;} + } + } + return -1; +} + +function findClosingParen(text: string, openPos: number): number { + let depth = 0; + for (let i = openPos; i < text.length; i++) { + if (text[i] === "\\" && i + 1 < text.length) { + i++; + continue; + } + if (text[i] === "(") {depth++;} + if (text[i] === ")") { + depth--; + if (depth === 0) {return i;} + } + } + return -1; +} + +/** + * Parse the inside of `(...)` from a link/image (the URL and optional title). + * Handles three URL forms: + * - bare: `/uri` or `/uri "title"` + * - angle-bracket: `` or ` "title"` (brackets are stripped) + * And three title-quote forms: `"..."`, `'...'`, `(...)`. + */ +function parseDestinationAndTitle(raw: string): { + url: string; + title?: string; +} { + raw = raw.trim(); + let url: string; + let rest: string; + + if (raw.startsWith("<")) { + const close = raw.indexOf(">"); + if (close === -1) { + // Unmatched `<` — treat the whole thing as the URL minus the `<`. + url = raw.substring(1); + rest = ""; + } else { + url = raw.substring(1, close); + rest = raw.substring(close + 1).trim(); + } + } else { + // Split at first unescaped whitespace. + let split = raw.length; + for (let i = 0; i < raw.length; i++) { + if (raw[i] === "\\" && i + 1 < raw.length) { + i++; + continue; + } + if (raw[i] === " " || raw[i] === "\t" || raw[i] === "\n") { + split = i; + break; + } + } + url = raw.substring(0, split); + rest = raw.substring(split).trim(); + } + + let title: string | undefined; + if (rest.length > 0) { + const titleMatch = rest.match(/^"([^"]*)"$|^'([^']*)'$|^\(([^)]*)\)$/); + if (titleMatch) { + title = titleMatch[1] ?? titleMatch[2] ?? titleMatch[3]; + } + } + + return { url, title }; +} + +function parseDelimited( + text: string, + start: number, + delimiter: string, + openTag: string, + closeTag: string +): { html: string; end: number } | null { + const len = delimiter.length; + const afterOpen = start + len; + + if (afterOpen >= text.length) {return null;} + + // Opening delimiter must not be followed by whitespace + if (text[afterOpen] === " " || text[afterOpen] === "\t") {return null;} + + // Find closing delimiter + let j = afterOpen; + while (j < text.length) { + // Skip escaped characters + if (text[j] === "\\" && j + 1 < text.length) { + j += 2; + continue; + } + + if (text.substring(j, j + len) === delimiter) { + // Closing delimiter must not be preceded by whitespace + if (text[j - 1] === " " || text[j - 1] === "\t") { + j++; + continue; + } + + // For single-char delimiters, don't accept closer if it's part of a + // multi-char run (e.g., don't treat the * in ** as italic closer) + if ( + len === 1 && + ((j > 0 && text[j - 1] === delimiter[0] && !(j >= 2 && text[j - 2] === "\\")) || + (j + len < text.length && text[j + len] === delimiter[0])) + ) { + j++; + continue; + } + + const inner = text.substring(afterOpen, j); + if (inner.length === 0) { + j++; + continue; + } + + return { + html: openTag + parseInline(inner) + closeTag, + end: j + len, + }; + } + j++; + } + + return null; +} + +// ─── Block-Level Types ─────────────────────────────────────────────────────── + +interface BlockToken { + type: string; +} + +interface HeadingToken extends BlockToken { + type: "heading"; + level: number; + content: string; +} + +interface ParagraphToken extends BlockToken { + type: "paragraph"; + content: string; +} + +interface CodeBlockToken extends BlockToken { + type: "codeBlock"; + language: string; + code: string; +} + +interface BlockquoteToken extends BlockToken { + type: "blockquote"; + content: string; +} + +interface HorizontalRuleToken extends BlockToken { + type: "hr"; +} + +interface ListItemToken extends BlockToken { + type: "listItem"; + listType: "bullet" | "ordered" | "task"; + indent: number; + content: string; + start?: number; // for ordered lists + checked?: boolean; // for task lists + childContent?: string; // recursively parsed content within this item +} + +interface TableToken extends BlockToken { + type: "table"; + headers: string[]; + rows: string[][]; + alignments: ("left" | "center" | "right" | null)[]; +} + +interface RawHtmlToken extends BlockToken { + type: "rawHtml"; + content: string; +} + +type Token = + | HeadingToken + | ParagraphToken + | CodeBlockToken + | BlockquoteToken + | HorizontalRuleToken + | ListItemToken + | TableToken + | RawHtmlToken; + +/** + * HTML block-level tag names (from the CommonMark type-6 list, plus `audio` + * which BlockNote serializes as raw HTML since markdown has no shorthand + * for it). When a line starts with `<` followed by one of these tag names, + * the run of non-blank lines is emitted verbatim as raw HTML rather than + * wrapped in a paragraph. + */ +const HTML_BLOCK_TAGS = new Set([ + "address", "article", "aside", "audio", "base", "basefont", "blockquote", + "body", "caption", "center", "col", "colgroup", "dd", "details", "dialog", + "dir", "div", "dl", "dt", "fieldset", "figcaption", "figure", "footer", + "form", "frame", "frameset", "h1", "h2", "h3", "h4", "h5", "h6", "head", + "header", "hr", "html", "iframe", "legend", "li", "link", "main", "menu", + "menuitem", "nav", "noframes", "ol", "optgroup", "option", "p", "param", + "section", "source", "summary", "table", "tbody", "td", "tfoot", "th", + "thead", "title", "tr", "track", "ul", +]); + +function isHtmlBlockStart(line: string): boolean { + // `, ``, ``, or ``. + // Lines are emitted verbatim until the next blank line. + if (isHtmlBlockStart(line)) { + const htmlLines: string[] = []; + while (i < lines.length && lines[i].trim() !== "") { + htmlLines.push(lines[i]); + i++; + } + tokens.push({ + type: "rawHtml", + content: htmlLines.join("\n"), + }); + prevLineWasBlank = false; + continue; + } + + // Paragraph (default) + const paraLines: string[] = [line]; + i++; + while (i < lines.length) { + const nextLine = lines[i]; + // Stop paragraph on blank line + if (nextLine.trim() === "") {break;} + // Stop on block-level element + if (/^(#{1,6})\s/.test(nextLine)) {break;} + if (/^(`{3,}|~{3,})/.test(nextLine)) {break;} + if (/^\s{0,3}>/.test(nextLine)) {break;} + if (/^(\s{0,3})([-*_])\s*(\2\s*){2,}$/.test(nextLine)) {break;} + if (/^\s*([-*+]|\d+[.)])\s+/.test(nextLine)) {break;} + if (/^\s*\|(.+\|)+\s*$/.test(nextLine)) {break;} + if (isHtmlBlockStart(nextLine)) {break;} + // Check if next-next line is setext marker + if ( + i + 1 < lines.length && + /^[=-]+\s*$/.test(lines[i + 1]) && + nextLine.trim().length > 0 + ) { + break; + } + paraLines.push(nextLine); + i++; + } + // CommonMark allows up to 3 leading spaces of indent on paragraph lines. + // Also strip trailing whitespace from the final line so a trailing + // hard-break sequence (` \n` at end of paragraph) doesn't leak as + // literal trailing spaces in the rendered output. + tokens.push({ + type: "paragraph", + content: paraLines + .map((l) => l.replace(/^ {1,3}/, "")) + .join("\n") + .replace(/[ \t]+$/, ""), + }); + prevLineWasBlank = false; + } + + return tokens; +} + +function tryParseTable( + lines: string[], + start: number +): { token: TableToken; nextLine: number } | null { + // A table needs at least a header row and a separator row + if (start + 1 >= lines.length) {return null;} + + const headerLine = lines[start]; + const separatorLine = lines[start + 1]; + + // Check separator line format: | --- | --- | or --- | --- (outer pipes optional) + // Must contain at least one pipe and only dashes, colons, pipes, and whitespace + if ( + !separatorLine.includes("|") || + !/^\s*\|?\s*:?-+:?\s*(\|\s*:?-+:?\s*)*\|?\s*$/.test(separatorLine) + ) {return null;} + + // Check header line has at least one pipe (required to distinguish from plain text) + if (!headerLine.includes("|")) {return null;} + + const headers = parsePipeCells(headerLine); + const alignments = parseAlignments(separatorLine); + + const rows: string[][] = []; + let i = start + 2; + while (i < lines.length) { + const line = lines[i]; + if (!line.includes("|")) {break;} + rows.push(parsePipeCells(line)); + i++; + } + + return { + token: { + type: "table", + headers, + rows, + alignments, + }, + nextLine: i, + }; +} + +function parsePipeCells(line: string): string[] { + // Trim leading/trailing pipes and split + const trimmed = line.trim(); + const withoutOuterPipes = trimmed.startsWith("|") + ? trimmed.substring(1) + : trimmed; + const content = withoutOuterPipes.endsWith("|") + ? withoutOuterPipes.substring(0, withoutOuterPipes.length - 1) + : withoutOuterPipes; + + // Split by pipes, handling escaped pipes + const cells: string[] = []; + let current = ""; + for (let i = 0; i < content.length; i++) { + if (content[i] === "\\" && i + 1 < content.length && content[i + 1] === "|") { + current += "|"; + i++; + } else if (content[i] === "|") { + cells.push(current.trim()); + current = ""; + } else { + current += content[i]; + } + } + cells.push(current.trim()); + + return cells; +} + +function parseAlignments( + separatorLine: string +): ("left" | "center" | "right" | null)[] { + const cells = parsePipeCells(separatorLine); + return cells.map((cell) => { + const trimmed = cell.trim(); + const left = trimmed.startsWith(":"); + const right = trimmed.endsWith(":"); + if (left && right) {return "center";} + if (right) {return "right";} + if (left) {return "left";} + return null; + }); +} + +// ─── HTML Emitter ──────────────────────────────────────────────────────────── + +function tokensToHtml(tokens: Token[]): string { + let html = ""; + let i = 0; + + while (i < tokens.length) { + const token = tokens[i]; + + switch (token.type) { + case "heading": { + const t = token as HeadingToken; + html += `${parseInline(t.content)}`; + i++; + break; + } + + case "paragraph": { + const t = token as ParagraphToken; + html += `

${parseInline(t.content)}

`; + i++; + break; + } + + case "codeBlock": { + const t = token as CodeBlockToken; + const langAttr = t.language + ? ` data-language="${escapeHtml(t.language)}"` + : ""; + html += `
${escapeHtml(t.code)}
`; + i++; + break; + } + + case "blockquote": { + const t = token as BlockquoteToken; + // Recursively parse blockquote content as markdown + const innerTokens = tokenize(t.content); + const innerHtml = tokensToHtml(innerTokens); + html += `
${innerHtml}
`; + i++; + break; + } + + case "hr": + html += `
`; + i++; + break; + + case "listItem": { + // Collect consecutive list items and build nested list structure + const listHtml = emitListItems(tokens, i); + html += listHtml.html; + i = listHtml.nextIndex; + break; + } + + case "table": { + const t = token as TableToken; + html += emitTable(t); + i++; + break; + } + + case "rawHtml": { + const t = token as RawHtmlToken; + html += t.content; + i++; + break; + } + + default: + i++; + } + } + + return html; +} + +function emitListItems( + tokens: Token[], + startIdx: number +): { html: string; nextIndex: number } { + let html = ""; + let i = startIdx; + let currentListType: "bullet" | "ordered" | null = null; + + while (i < tokens.length && tokens[i].type === "listItem") { + const item = tokens[i] as ListItemToken; + const effectiveType = getEffectiveListType(item.listType); + + // Check if we need to switch list type + if (currentListType !== null && currentListType !== effectiveType) { + // Close current list, open new one + html += ``; + currentListType = null; + } + + // Open list if needed + if (currentListType === null) { + if (effectiveType === "ordered") { + const startAttr = + item.start !== undefined && item.start !== 1 + ? ` start="${item.start}"` + : ""; + html += ``; + } else { + html += `
diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockFullContent.html b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockFullContent.html
new file mode 100644
index 0000000000..2d3784f358
--- /dev/null
+++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockFullContent.html
@@ -0,0 +1,3 @@
+const a = 1;
+
+const b = 2; \ No newline at end of file diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockPartialSelection.html b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockPartialSelection.html new file mode 100644 index 0000000000..8ff30e3da7 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/codeBlockPartialSelection.html @@ -0,0 +1,3 @@ +onst a = 1; +
+const b = 2 \ No newline at end of file diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/image.html b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/image.html index 8b48774f5b..dbeb38ec30 100644 --- a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/image.html +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/image.html @@ -1,5 +1,5 @@ BlockNote image \ No newline at end of file diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/nestedImage.html b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/nestedImage.html index ece8708071..45b2830cd7 100644 --- a/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/nestedImage.html +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/html/nestedImage.html @@ -1,7 +1,7 @@

Paragraph 1

BlockNote image diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md new file mode 100644 index 0000000000..59e94f2356 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocks.md @@ -0,0 +1,24 @@ +Paragraph 1 + +# Heading 1 + +1. Numbered List Item 1 + +* Bullet List Item 1 +* [ ] Check List Item 1 +* Toggle List Item 1 + +```text +console.log("Hello World"); +``` + +| | | +| ------------ | ------------ | +| Table Cell 1 | Table Cell 2 | +| Table Cell 3 | Table Cell 4 | + + + +*** + +Paragraph 2 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocksWithProps.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocksWithProps.md new file mode 100644 index 0000000000..906cfc560e --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/basicBlocksWithProps.md @@ -0,0 +1,24 @@ +Paragraph 1 + +## Heading 1 + +2. Numbered List Item 1 + +* Bullet List Item 1 +* [x] Check List Item 1 +* Toggle List Item 1 + +```typescript +console.log("Hello World"); +``` + +| | | +| ------------ | ------------ | +| Table Cell 1 | Table Cell 2 | +| Table Cell 3 | Table Cell 4 | + +
1280px-Placeholder_view_vector.svg.png
Placeholder
+ +*** + +Paragraph 2 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childToParent.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childToParent.md new file mode 100644 index 0000000000..c843d3b34c --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childToParent.md @@ -0,0 +1,3 @@ +Paragraph 1 + +Nested Paragraph 1 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParent.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParent.md new file mode 100644 index 0000000000..a1c8bcb403 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParent.md @@ -0,0 +1,7 @@ +Nested Paragraph 1 + +Nested Paragraph 2 + +Nested Paragraph 3 + +Paragraph 2 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParentsChildren.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParentsChildren.md new file mode 100644 index 0000000000..3350bf4006 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/childrenToNextParentsChildren.md @@ -0,0 +1,13 @@ +Nested Paragraph 1 + +Nested Paragraph 2 + +Nested Paragraph 3 + +Paragraph 2 + +Nested Paragraph 4 + +Nested Paragraph 5 + +Nested Paragraph 6 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockFullContent.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockFullContent.md new file mode 100644 index 0000000000..781d5f3999 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockFullContent.md @@ -0,0 +1,2 @@ +const a = 1; +const b = 2; \ No newline at end of file diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockPartialSelection.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockPartialSelection.md new file mode 100644 index 0000000000..b911c40cba --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/codeBlockPartialSelection.md @@ -0,0 +1,2 @@ +onst a = 1; +const b = 2 \ No newline at end of file diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/image.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/image.md new file mode 100644 index 0000000000..02f1b078b9 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/image.md @@ -0,0 +1 @@ +![](https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg) diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/mentionWithBackgroundColor.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/mentionWithBackgroundColor.md new file mode 100644 index 0000000000..430c08db48 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/mentionWithBackgroundColor.md @@ -0,0 +1,5 @@ +Paragraph 1 + +@User + +Paragraph 2 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleChildren.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleChildren.md new file mode 100644 index 0000000000..cfb1e11c1c --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleChildren.md @@ -0,0 +1,5 @@ +Nested Paragraph 1 + +Nested Paragraph 2 + +Nested Paragraph 3 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleStyledText.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleStyledText.md new file mode 100644 index 0000000000..2a9e4dfd67 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/multipleStyledText.md @@ -0,0 +1 @@ +Unstyled TextItalic TextBold Text diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/nestedImage.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/nestedImage.md new file mode 100644 index 0000000000..8ac9591d8a --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/nestedImage.md @@ -0,0 +1,5 @@ +Paragraph 1 + +![](https://ralfvanveen.com/wp-content/uploads/2021/06/Placeholder-_-Glossary.svg) + +Nested Paragraph 1 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/partialChildToParent.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/partialChildToParent.md new file mode 100644 index 0000000000..d8e105dbc6 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/partialChildToParent.md @@ -0,0 +1,3 @@ +aragraph 1 + +N diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/styledText.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/styledText.md new file mode 100644 index 0000000000..e0c33d6a00 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/styledText.md @@ -0,0 +1 @@ +Italic Text diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableAllCells.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableAllCells.md new file mode 100644 index 0000000000..4cce45d6ef --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableAllCells.md @@ -0,0 +1,4 @@ +| | | +| ------------ | ------------ | +| Table Cell 1 | Table Cell 2 | +| Table Cell 3 | Table Cell 4 | diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCell.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCell.md new file mode 100644 index 0000000000..e4a55e08be --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCell.md @@ -0,0 +1,3 @@ +| | +| ------------ | +| Table Cell 1 | diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCellText.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCellText.md new file mode 100644 index 0000000000..bd5288ec6d --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCellText.md @@ -0,0 +1 @@ +Table Cell 1 diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCol.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCol.md new file mode 100644 index 0000000000..54fa7b06a2 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableCol.md @@ -0,0 +1,4 @@ +| | +| ------------ | +| Table Cell 1 | +| Table Cell 3 | diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableRow.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableRow.md new file mode 100644 index 0000000000..4489a26e88 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/tableRow.md @@ -0,0 +1,3 @@ +| | | +| ------------ | ------------ | +| Table Cell 1 | Table Cell 2 | diff --git a/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/unstyledText.md b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/unstyledText.md new file mode 100644 index 0000000000..44a4f52867 --- /dev/null +++ b/tests/src/unit/core/clipboard/copy/__snapshots__/text/plain/unstyledText.md @@ -0,0 +1 @@ +Unstyled Text diff --git a/tests/src/unit/core/clipboard/copy/copyTestInstances.ts b/tests/src/unit/core/clipboard/copy/copyTestInstances.ts index 17fa89e08d..4bd34489c0 100644 --- a/tests/src/unit/core/clipboard/copy/copyTestInstances.ts +++ b/tests/src/unit/core/clipboard/copy/copyTestInstances.ts @@ -7,7 +7,10 @@ import { TestStyleSchema, } from "../../testSchema.js"; import { CopyTestCase } from "../../../shared/clipboard/copy/copyTestCase.js"; -import { testCopyHTML } from "../../../shared/clipboard/copy/copyTestExecutors.js"; +import { + testCopyHTML, + testCopyMarkdown, +} from "../../../shared/clipboard/copy/copyTestExecutors.js"; import { getPosOfTableCellNode, getPosOfTextNode, @@ -676,4 +679,61 @@ export const copyTestInstancesHTML: TestInstance< }, executeTest: testCopyHTML, }, + { + testCase: { + name: "codeBlockFullContent", + document: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const a = 1;\nconst b = 2;", + }, + ], + getCopySelection: (doc) => { + const text = "const a = 1;\nconst b = 2;"; + const startPos = getPosOfTextNode(doc, text); + const endPos = getPosOfTextNode(doc, text, true); + + return TextSelection.create(doc, startPos, endPos); + }, + }, + executeTest: testCopyHTML, + }, + { + testCase: { + name: "codeBlockPartialSelection", + document: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const a = 1;\nconst b = 2;", + }, + ], + getCopySelection: (doc) => { + const text = "const a = 1;\nconst b = 2;"; + const startPos = getPosOfTextNode(doc, text); + // Select `onst a = 1;\nconst b = 2` — partial, spans a newline. + return TextSelection.create( + doc, + startPos + "c".length, + startPos + text.length - ";".length, + ); + }, + }, + executeTest: testCopyHTML, + }, ]; + +// text/plain payloads — exercises the same selections as above but snapshots +// the markdown output. Code-block selections should emit raw text (no markdown +// fences or `\` line-break escaping) so the clipboard round-trips back into a +// code block without leaving leftover syntax. +export const copyTestInstancesMarkdown: TestInstance< + CopyTestCase, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = copyTestInstancesHTML.map(({ testCase }) => ({ + testCase, + executeTest: testCopyMarkdown, +})); diff --git a/tests/src/unit/core/clipboard/copy/runTests.test.ts b/tests/src/unit/core/clipboard/copy/runTests.test.ts index f7f2474d03..eb2770b4ab 100644 --- a/tests/src/unit/core/clipboard/copy/runTests.test.ts +++ b/tests/src/unit/core/clipboard/copy/runTests.test.ts @@ -2,7 +2,10 @@ import { describe, it } from "vitest"; import { createTestEditor } from "../../createTestEditor.js"; import { testSchema } from "../../testSchema.js"; -import { copyTestInstancesHTML } from "./copyTestInstances.js"; +import { + copyTestInstancesHTML, + copyTestInstancesMarkdown, +} from "./copyTestInstances.js"; // Tests for verifying content that gets put on the clipboard when copying // within the editor. Used for as many cases as possible to ensure each block or @@ -16,3 +19,13 @@ describe("Copy tests (HTML)", () => { }); } }); + +describe("Copy tests (Markdown)", () => { + const getEditor = createTestEditor(testSchema); + + for (const { testCase, executeTest } of copyTestInstancesMarkdown) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/core/clipboard/paste/__snapshots__/text/html/pasteHTMLWithMultipleCheckboxesInTableCell.json b/tests/src/unit/core/clipboard/paste/__snapshots__/text/html/pasteHTMLWithMultipleCheckboxesInTableCell.json index adfce7adf4..e2cf22f640 100644 --- a/tests/src/unit/core/clipboard/paste/__snapshots__/text/html/pasteHTMLWithMultipleCheckboxesInTableCell.json +++ b/tests/src/unit/core/clipboard/paste/__snapshots__/text/html/pasteHTMLWithMultipleCheckboxesInTableCell.json @@ -16,8 +16,8 @@ { "styles": {}, "text": "Cell 1ABC - Unit tests covering the new feature have been added. - All existing tests pass.", +Unit tests covering the new feature have been added. +All existing tests pass.", "type": "text", }, ], diff --git a/tests/src/unit/core/clipboard/paste/pasteTestInstances.ts b/tests/src/unit/core/clipboard/paste/pasteTestInstances.ts index cf9e0d33dd..0220d816d9 100644 --- a/tests/src/unit/core/clipboard/paste/pasteTestInstances.ts +++ b/tests/src/unit/core/clipboard/paste/pasteTestInstances.ts @@ -1,10 +1,5 @@ import { TextSelection } from "@tiptap/pm/state"; -import { - TestBlockSchema, - TestInlineContentSchema, - TestStyleSchema, -} from "../../testSchema.js"; import { PasteTestCase } from "../../../shared/clipboard/paste/pasteTestCase.js"; import { testPasteHTML, @@ -12,6 +7,11 @@ import { } from "../../../shared/clipboard/paste/pasteTestExecutors.js"; import { getPosOfTextNode } from "../../../shared/testUtil.js"; import { TestInstance } from "../../../types.js"; +import { + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema, +} from "../../testSchema.js"; export const pasteTestInstancesHTML: TestInstance< PasteTestCase, diff --git a/tests/src/unit/core/createTestEditor.ts b/tests/src/unit/core/createTestEditor.ts index b556ae5dc4..972e42b14d 100644 --- a/tests/src/unit/core/createTestEditor.ts +++ b/tests/src/unit/core/createTestEditor.ts @@ -39,6 +39,12 @@ export const createTestEditor = < }), }, }), + links: { + HTMLAttributes: { + rel: "external", + "data-custom-attribute": true, + }, + }, tables: { splitCells: true, cellBackgroundColor: true, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/basic.html new file mode 100644 index 0000000000..bba236e2d5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/basic.html @@ -0,0 +1,22 @@ +
+
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/button.html new file mode 100644 index 0000000000..2dceaf9f4f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/button.html @@ -0,0 +1,20 @@ +
+
+
+
+
+
+
+ + + +
+

Add audio

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/noName.html new file mode 100644 index 0000000000..9753d9671a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/audio/noName.html @@ -0,0 +1,21 @@ +
+
+
+
+
+ +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html index f169947d1e..bf789c1a7d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/contains-newlines.html @@ -13,7 +13,9 @@
-          const hello ='world';console.log(hello);
+          const hello = 'world';
+console.log(hello);
+
         
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html index c25f830ff4..ce97dbaaac 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/codeBlock/empty.html @@ -9,9 +9,7 @@
-          
-            
-          
+          
         
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html new file mode 100644 index 0000000000..4376ebf7f1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/complex/document.html @@ -0,0 +1,90 @@ +
+
+
+
+

Document Title

+
+
+
+
+
+
+

Introduction paragraph.

+
+
+
+
+
+
+

Section 1

+
+
+
+
+
+
+

+ Text with + bold + and + a link + . +

+
+
+
+
+
+
+

First point

+
+
+
+
+
+
+

Second point

+
+
+
+
+
+
+
+
+
+
+
+
+
+
A notable quote
+
+
+
+
+
+
+
+ +
+
+          const x = 42;
+        
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html index 9974d8d975..d7802da3e3 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/basic.html @@ -9,7 +9,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -20,8 +20,8 @@

example

-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html index 6553a5c4a8..c62486d27d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/nested.html @@ -9,7 +9,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -20,8 +20,8 @@

example

-

Caption

-
+
Caption
+
@@ -34,7 +34,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -45,8 +45,8 @@

example

-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html index 47ae5b3bf9..1ec20af747 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/file/noName.html @@ -8,7 +8,7 @@ data-caption="Caption" data-file-block="" > -
+
@@ -19,8 +19,8 @@

-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html index 9e4b427c62..cbc42aa3f9 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/between-links.html @@ -4,15 +4,21 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html index 4cae02d67b..a896507f92 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/hardbreak/link.html @@ -4,15 +4,21 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html new file mode 100644 index 0000000000..3e3e513852 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h1.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 1

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html new file mode 100644 index 0000000000..7ffe42afa3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h2.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 2

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html new file mode 100644 index 0000000000..437867e0d9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h3.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 3

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html new file mode 100644 index 0000000000..1ef4e627ff --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h4.html @@ -0,0 +1,9 @@ +
+
+
+
+

Heading 4

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html new file mode 100644 index 0000000000..f44690aa57 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h5.html @@ -0,0 +1,9 @@ +
+
+
+
+
Heading 5
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html new file mode 100644 index 0000000000..5daca5fbdf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/h6.html @@ -0,0 +1,9 @@ +
+
+
+
+
Heading 6
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html new file mode 100644 index 0000000000..31df3416ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/styled.html @@ -0,0 +1,12 @@ +
+
+
+
+

+ Bold + Heading +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html new file mode 100644 index 0000000000..2982ce3673 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/heading/toggleable.html @@ -0,0 +1,38 @@ +
+
+
+
+
+
+ +

Toggle Heading

+
+
+
+
+
+
+
+

Child content

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html index c26a9764e1..a17b1d3982 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image.html @@ -15,9 +15,11 @@ BlockNote image + +
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html index 586f875965..8700c93f57 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/basic.html @@ -10,15 +10,23 @@ data-preview-width="256" data-file-block="" > -
- example + example + +
-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html index a36d9be3f1..b7d2064ce9 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/nested.html @@ -9,15 +9,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+
@@ -30,15 +38,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html index 5f2ec1644c..44b706cebe 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noCaption.html @@ -14,7 +14,15 @@ style="position: relative; width: 256px;" >
- example + example + +
diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html index 9403217ea6..2a2bbd7a8d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noName.html @@ -9,15 +9,23 @@ data-preview-width="256" data-file-block="" > -
- Caption + + +
-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html index 3e1f5a6264..59d65f82ae 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/noPreview.html @@ -11,7 +11,7 @@ data-preview-width="256" data-file-block="" > -
+
@@ -22,8 +22,8 @@

example

-

Caption

-
+
Caption
+ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html new file mode 100644 index 0000000000..6940729386 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/urlOnly.html @@ -0,0 +1,23 @@ +
+
+
+
+
+
+ + + +
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html new file mode 100644 index 0000000000..5844c98607 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/image/withCaption.html @@ -0,0 +1,31 @@ +
+
+
+
+
+
+ Example Image + + +
+
This is a caption
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html index 2408c611ac..9af317b375 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/adjacent.html @@ -4,14 +4,20 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html index 3daea90831..159dfce9ea 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/basic.html @@ -4,9 +4,12 @@ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html new file mode 100644 index 0000000000..db540213b4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/plainUrl.html @@ -0,0 +1,18 @@ +
+
+ +
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html index 2b9d4cb574..766681b45a 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/styled.html @@ -5,15 +5,21 @@

Web site

diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html new file mode 100644 index 0000000000..ab632bd13f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/urlWithParens.html @@ -0,0 +1,18 @@ +
+
+
+
+

+ Example +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html new file mode 100644 index 0000000000..45427ff9ad --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/link/withCode.html @@ -0,0 +1,21 @@ +
+
+
+
+

+ See the + docs + for + config +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html new file mode 100644 index 0000000000..62b0458466 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/numberedListStart.html @@ -0,0 +1,25 @@ +
+
+
+
+

Item 5

+
+
+
+
+
+
+

Item 6

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html new file mode 100644 index 0000000000..018c41520e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/lists/toggleWithChildren.html @@ -0,0 +1,76 @@ +
+
+
+
+
+
+ +

Toggle List Item

+
+
+
+
+
+
+
+

Toggle Child 1

+
+
+
+
+
+
+

Toggle Child 2

+
+
+
+
+
+
+
+
+
+
+
+ +

Toggle Heading

+
+
+
+
+
+
+
+

Heading Child 1

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html new file mode 100644 index 0000000000..a5c65cdb68 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/paragraph/multiple.html @@ -0,0 +1,23 @@ +
+
+
+
+

First paragraph

+
+
+
+
+
+
+

Second paragraph

+
+
+
+
+
+
+

Third paragraph

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html new file mode 100644 index 0000000000..278aafa3ec --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/basic.html @@ -0,0 +1,9 @@ +
+
+
+
+
This is a quote
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html new file mode 100644 index 0000000000..99c7aa530e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/multiple.html @@ -0,0 +1,16 @@ +
+
+
+
+
First quote
+
+
+
+
+
+
+
Second quote
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html new file mode 100644 index 0000000000..da3b3d1215 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/nested.html @@ -0,0 +1,18 @@ +
+
+
+
+
Parent quote
+
+
+
+
+
+

Nested paragraph

+
+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html new file mode 100644 index 0000000000..244868c2c6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/styled.html @@ -0,0 +1,14 @@ +
+
+
+
+
+ Bold + and + italic + quote +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html new file mode 100644 index 0000000000..5cbacbdec1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/quote/withLink.html @@ -0,0 +1,19 @@ +
+
+
+
+
+ Quote with + a link +
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html new file mode 100644 index 0000000000..da1b939f67 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/backgroundColor.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Highlighted text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html new file mode 100644 index 0000000000..1834b5c3ca --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/bold.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Bold text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html new file mode 100644 index 0000000000..f8baaa4507 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/boldItalicStrike.html @@ -0,0 +1,15 @@ +
+
+
+
+

+ + + All styles + + +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html new file mode 100644 index 0000000000..31aa7a6dba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/code.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Inline code +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html new file mode 100644 index 0000000000..3bcf4491b2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/combined.html @@ -0,0 +1,13 @@ +
+
+
+
+

+ + Bold and italic + +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html new file mode 100644 index 0000000000..265708f07d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/italic.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Italic text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html new file mode 100644 index 0000000000..b8f217f641 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/mixedInParagraph.html @@ -0,0 +1,15 @@ +
+
+
+
+

+ Normal + bold + italic + code + strike +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html new file mode 100644 index 0000000000..294425c21f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/strike.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Strikethrough text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html new file mode 100644 index 0000000000..0e6799d766 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/textColor.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Colored text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html new file mode 100644 index 0000000000..29ab7e88cf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/style/underline.html @@ -0,0 +1,11 @@ +
+
+
+
+

+ Underline text +

+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html new file mode 100644 index 0000000000..1327b27445 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/advancedExample.html @@ -0,0 +1,82 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + + + + + + + + +
+

This row has headers

+
+

+ This is + RED +

+
+

Text is Blue

+
+

+ This spans 2 columns +
+ and 2 rows +

+
+

Sooo many features

+
+

+
+

A cell

+
+

Another Cell

+
+

Aligned center

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html new file mode 100644 index 0000000000..8b1b1756b0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/cellTextAlignment.html @@ -0,0 +1,33 @@ +
+
+
+
+
+
+ + + + + + + + + + + +
+

Left

+
+

Center

+
+

Right

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html new file mode 100644 index 0000000000..b3564081f0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/emptyCells.html @@ -0,0 +1,37 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

Has content

+
+

+
+

+
+

Also has content

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html new file mode 100644 index 0000000000..d0c5618ffc --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/hardBreakInCell.html @@ -0,0 +1,33 @@ +
+
+
+
+
+
+ + + + + + + + + +
+

+ Line 1 +
+ Line 2 +

+
+

Normal cell

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html new file mode 100644 index 0000000000..945741a785 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/headerRowsAndCols.html @@ -0,0 +1,44 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + + + + +
+

Corner

+
+

Column Header 1

+
+

Column Header 2

+
+

Row Header 1

+
+

Data 1

+
+

Data 2

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html new file mode 100644 index 0000000000..8aa07b4b98 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/linksInCells.html @@ -0,0 +1,56 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

+ Visit + Example +

+
+

Plain cell

+
+

Data

+
+

+ Link +

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html new file mode 100644 index 0000000000..63c4da11a1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/singleCell.html @@ -0,0 +1,25 @@ +
+
+
+
+
+
+ + + + + + + +
+

Only cell

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html new file mode 100644 index 0000000000..9d8035d5c5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/table/styledCellContent.html @@ -0,0 +1,45 @@ +
+
+
+
+
+
+ + + + + + + + + + + + + +
+

+ Bold +

+
+

+ Italic +

+
+

+ Strike +

+
+

+ Code +

+
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html index 916688f455..78babe7a23 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video.html @@ -17,8 +17,9 @@ src="https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm" controls="" draggable="false" - width="0" > + + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html new file mode 100644 index 0000000000..951b9d8b86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/blocknoteHTML/video/withCaption.html @@ -0,0 +1,31 @@ +
+
+
+
+
+
+ + + +
+
Video caption
+
+
+
+
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html new file mode 100644 index 0000000000..d2a69001ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/basic.html @@ -0,0 +1,5 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html new file mode 100644 index 0000000000..915743c700 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/button.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html new file mode 100644 index 0000000000..1699b58c73 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/audio/noName.html @@ -0,0 +1,4 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html index df88fa0937..a7db81b06b 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/codeBlock/contains-newlines.html @@ -1,8 +1,5 @@
-  
-    const hello ='world';
-    
- console.log(hello); -
-
+ const hello = 'world'; +console.log(hello); +
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html new file mode 100644 index 0000000000..421d420c08 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/complex/document.html @@ -0,0 +1,30 @@ +

Document Title

+

Introduction paragraph.

+

Section 1

+

+ Text with + bold + and + a link + . +

+
    +
  • +

    First point

    +
  • +
  • +

    Second point

    +
  • +
+
+
A notable quote
+
+  const x = 42;
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html index cc675c57a7..90ce06d701 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/file/button.html @@ -1 +1 @@ -

Add file

\ No newline at end of file + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html index 701b5d4213..54664cf2b1 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/between-links.html @@ -1,13 +1,19 @@

Link1
Link2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html index 2c762aedc5..930c29aaab 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/hardbreak/link.html @@ -1,13 +1,19 @@

Link1
Link1

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html new file mode 100644 index 0000000000..ac06cdc123 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h1.html @@ -0,0 +1 @@ +

Heading 1

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html new file mode 100644 index 0000000000..92e9734754 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h2.html @@ -0,0 +1 @@ +

Heading 2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html new file mode 100644 index 0000000000..df25998db1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h3.html @@ -0,0 +1 @@ +

Heading 3

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html new file mode 100644 index 0000000000..430144bc54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h4.html @@ -0,0 +1 @@ +

Heading 4

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html new file mode 100644 index 0000000000..02e7e8fda2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h5.html @@ -0,0 +1 @@ +
Heading 5
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html new file mode 100644 index 0000000000..6e76905810 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/h6.html @@ -0,0 +1 @@ +
Heading 6
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html new file mode 100644 index 0000000000..7f14fdb711 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/styled.html @@ -0,0 +1,4 @@ +

+ Bold + Heading +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html new file mode 100644 index 0000000000..ecec05b566 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/heading/toggleable.html @@ -0,0 +1,6 @@ +
+ +

Toggle Heading

+
+

Child content

+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html index ef7342fe92..a9efaefefc 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image.html @@ -1,5 +1,5 @@ BlockNote image \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html index 8553433aff..df18852143 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/button.html @@ -1 +1 @@ -

Add image

\ No newline at end of file + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html index 04ccf17c56..667cef41ab 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/nested.html @@ -1,5 +1,5 @@
- Caption +
Caption
- Caption +
Caption
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html index 686fc7d4e5..47f0cbe255 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/noName.html @@ -1,4 +1,4 @@
- Caption +
Caption
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html new file mode 100644 index 0000000000..41960a99f8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/urlOnly.html @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html new file mode 100644 index 0000000000..3ecba73103 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/image/withCaption.html @@ -0,0 +1,8 @@ +
+ Example Image +
This is a caption
+
\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html index db99691d33..057be7e7f6 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/adjacent.html @@ -1,12 +1,18 @@

Website Website2

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html index 4b61e8c582..610e86b2dd 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/basic.html @@ -1,7 +1,10 @@

Website

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html new file mode 100644 index 0000000000..d014edd8bb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/plainUrl.html @@ -0,0 +1,10 @@ +

+ https://www.website.com +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html index fb7737f7f8..189a36f5e8 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/styled.html @@ -1,14 +1,20 @@

Web site

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html new file mode 100644 index 0000000000..a783db1f73 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/urlWithParens.html @@ -0,0 +1,10 @@ +

+ Example +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html new file mode 100644 index 0000000000..385c0be8c9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/link/withCode.html @@ -0,0 +1,13 @@ +

+ See the + docs + for + config +

\ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html index d0b661f0ce..1d9f613297 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/basic.html @@ -24,6 +24,10 @@

Check List Item 2

  • -

    Toggle List Item 1

    +
    + +

    Toggle List Item 1

    +
    +
  • \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html index fc16110695..113e6a5d08 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/nested.html @@ -20,7 +20,11 @@

    Check List Item 2

    • -

      Toggle List Item 1

      +
      + +

      Toggle List Item 1

      +
      +
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html new file mode 100644 index 0000000000..35535f7db4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/numberedListStart.html @@ -0,0 +1,8 @@ +
      +
    1. +

      Item 5

      +
    2. +
    3. +

      Item 6

      +
    4. +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html new file mode 100644 index 0000000000..3712487eb1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/lists/toggleWithChildren.html @@ -0,0 +1,17 @@ +
      +
    • +
      + +

      Toggle List Item

      +
      +

      Toggle Child 1

      +

      Toggle Child 2

      +
      +
    • +
    +
    + +

    Toggle Heading

    +
    +

    Heading Child 1

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html new file mode 100644 index 0000000000..a183a01cd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/paragraph/multiple.html @@ -0,0 +1,3 @@ +

    First paragraph

    +

    Second paragraph

    +

    Third paragraph

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html new file mode 100644 index 0000000000..53c51228f1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/basic.html @@ -0,0 +1 @@ +
    This is a quote
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html new file mode 100644 index 0000000000..80b8a40ae5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/multiple.html @@ -0,0 +1,2 @@ +
    First quote
    +
    Second quote
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html new file mode 100644 index 0000000000..3e74d08d92 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/nested.html @@ -0,0 +1,2 @@ +
    Parent quote
    +

    Nested paragraph

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html new file mode 100644 index 0000000000..7f80b7fc7c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/styled.html @@ -0,0 +1,6 @@ +
    + Bold + and + italic + quote +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html new file mode 100644 index 0000000000..c893fa67ee --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/quote/withLink.html @@ -0,0 +1,11 @@ +
    + Quote with + a link +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html new file mode 100644 index 0000000000..66f327e85d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/backgroundColor.html @@ -0,0 +1,8 @@ +

    + Highlighted text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html new file mode 100644 index 0000000000..e57a879f6e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/bold.html @@ -0,0 +1,3 @@ +

    + Bold text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html new file mode 100644 index 0000000000..d7506fe610 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/boldItalicStrike.html @@ -0,0 +1,7 @@ +

    + + + All styles + + +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html new file mode 100644 index 0000000000..6fe865b744 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/code.html @@ -0,0 +1,3 @@ +

    + Inline code +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html new file mode 100644 index 0000000000..920576e90a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/combined.html @@ -0,0 +1,5 @@ +

    + + Bold and italic + +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html new file mode 100644 index 0000000000..fcff5726e6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/italic.html @@ -0,0 +1,3 @@ +

    + Italic text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html new file mode 100644 index 0000000000..369fd1b3bf --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/mixedInParagraph.html @@ -0,0 +1,7 @@ +

    + Normal + bold + italic + code + strike +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html new file mode 100644 index 0000000000..abfabbe4e6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/strike.html @@ -0,0 +1,3 @@ +

    + Strikethrough text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html new file mode 100644 index 0000000000..798166dcad --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/textColor.html @@ -0,0 +1,8 @@ +

    + Colored text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html new file mode 100644 index 0000000000..f861031c9e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/style/underline.html @@ -0,0 +1,3 @@ +

    + Underline text +

    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html new file mode 100644 index 0000000000..a4f5dfcb31 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/advancedExample.html @@ -0,0 +1,67 @@ + + + + + + + + + + + + + + + + + + + + + + + +
    +

    This row has headers

    +
    +

    + This is + RED +

    +
    +

    Text is Blue

    +
    +

    + This spans 2 columns +
    + and 2 rows +

    +
    +

    Sooo many features

    +
    +

    +
    +

    A cell

    +
    +

    Another Cell

    +
    +

    Aligned center

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html new file mode 100644 index 0000000000..21f5f0ab0c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/cellTextAlignment.html @@ -0,0 +1,18 @@ + + + + + + + + + + + +
    +

    Left

    +
    +

    Center

    +
    +

    Right

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html new file mode 100644 index 0000000000..10a9fb5259 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/emptyCells.html @@ -0,0 +1,22 @@ + + + + + + + + + + + + + +
    +

    Has content

    +
    +

    +
    +

    +
    +

    Also has content

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html new file mode 100644 index 0000000000..a313a5323d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/hardBreakInCell.html @@ -0,0 +1,18 @@ + + + + + + + + + +
    +

    + Line 1 +
    + Line 2 +

    +
    +

    Normal cell

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html new file mode 100644 index 0000000000..64ee4183aa --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/headerRowsAndCols.html @@ -0,0 +1,29 @@ + + + + + + + + + + + + + + + + +
    +

    Corner

    +
    +

    Column Header 1

    +
    +

    Column Header 2

    +
    +

    Row Header 1

    +
    +

    Data 1

    +
    +

    Data 2

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html new file mode 100644 index 0000000000..0cafd0eda1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/linksInCells.html @@ -0,0 +1,41 @@ + + + + + + + + + + + + + +
    +

    + Visit + Example +

    +
    +

    Plain cell

    +
    +

    Data

    +
    +

    + Link +

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html new file mode 100644 index 0000000000..ce8bea5831 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/singleCell.html @@ -0,0 +1,10 @@ + + + + + + + +
    +

    Only cell

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html new file mode 100644 index 0000000000..91db4da0e4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/table/styledCellContent.html @@ -0,0 +1,30 @@ + + + + + + + + + + + + + +
    +

    + Bold +

    +
    +

    + Italic +

    +
    +

    + Strike +

    +
    +

    + Code +

    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html b/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html new file mode 100644 index 0000000000..978dcc0448 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/html/video/withCaption.html @@ -0,0 +1,8 @@ +
    + +
    Video caption
    +
    \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md new file mode 100644 index 0000000000..cdcf0dde54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/basic.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md new file mode 100644 index 0000000000..8b13789179 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/button.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md new file mode 100644 index 0000000000..cdcf0dde54 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/audio/noName.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md new file mode 100644 index 0000000000..47cf4739db --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/document.md @@ -0,0 +1,18 @@ +# Document Title + +Introduction paragraph. + +## Section 1 + +Text with **bold** and [a link](https://example.com). + +* First point +* Second point + +*** + +> A notable quote + +```javascript +const x = 42; +``` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md index 4a2de0a7fc..fca446bec3 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/complex/misc.md @@ -1,4 +1,4 @@ -## **Heading ***~~2~~* +## **Heading** *~~2~~* Paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md index 8d3fa6a207..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/button.md @@ -1 +1 @@ -Add file + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md index c7fefc547f..4cca42f87d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/file/noName.md @@ -1,3 +1,3 @@ -[exampleURL](exampleURL) +exampleURL Caption diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/hardbreak/only.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md new file mode 100644 index 0000000000..bd706e91c4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h1.md @@ -0,0 +1 @@ +# Heading 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md new file mode 100644 index 0000000000..cd760a44ba --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h2.md @@ -0,0 +1 @@ +## Heading 2 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md new file mode 100644 index 0000000000..607fcc43b6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h3.md @@ -0,0 +1 @@ +### Heading 3 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md new file mode 100644 index 0000000000..9c7bd7c52e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h4.md @@ -0,0 +1 @@ +#### Heading 4 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md new file mode 100644 index 0000000000..2410fdf2b0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h5.md @@ -0,0 +1 @@ +##### Heading 5 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md new file mode 100644 index 0000000000..848d83e6dd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/h6.md @@ -0,0 +1 @@ +###### Heading 6 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md new file mode 100644 index 0000000000..90c78848fd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/styled.md @@ -0,0 +1 @@ +# **Bold** Heading diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md new file mode 100644 index 0000000000..cc8cbf3aa9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/heading/toggleable.md @@ -0,0 +1,3 @@ +## Toggle Heading + +Child content diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md index 3219bb9f00..58d07ff1a4 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image.md @@ -1 +1 @@ -![BlockNote image](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png) +![](https://www.google.com/images/branding/googlelogo/1x/googlelogo_color_272x92dp.png) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md index b350ae21e0..5a88869a1b 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/basic.md @@ -1,3 +1 @@ -![example](exampleURL) - -Caption +
    example
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md index 02184caf8a..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/button.md @@ -1 +1 @@ -Add image + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md index 7a13551364..99ff1825d4 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/nested.md @@ -1,7 +1,3 @@ -![Caption](exampleURL) +
    Caption
    -Caption - -![Caption](exampleURL) - -Caption +
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md index c6b5864d90..ba1c350f13 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/noName.md @@ -1,3 +1 @@ -![Caption](exampleURL) - -Caption +
    Caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md new file mode 100644 index 0000000000..f667f8e031 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/urlOnly.md @@ -0,0 +1 @@ +![](exampleURL) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md new file mode 100644 index 0000000000..fb8426fd87 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/image/withCaption.md @@ -0,0 +1 @@ +
    Example Image
    This is a caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md new file mode 100644 index 0000000000..0ba1e54028 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/plainUrl.md @@ -0,0 +1 @@ +https://www.website.com diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md new file mode 100644 index 0000000000..ebe94f4ffe --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/urlWithParens.md @@ -0,0 +1 @@ +[Example](https://en.wikipedia.org/wiki/Example_\(disambiguation\)) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md new file mode 100644 index 0000000000..090ae185e1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/link/withCode.md @@ -0,0 +1 @@ +See the [docs](https://example.com) for `config` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md index f092d8bf95..a083f00804 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/basic.md @@ -1,13 +1,9 @@ * Bullet List Item 1 - * Bullet List Item 2 1. Numbered List Item 1 - 2. Numbered List Item 2 * [ ] Check List Item 1 - * [x] Check List Item 2 - * Toggle List Item 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md index c43ddb13ef..a0388ef96d 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/nested.md @@ -1,13 +1,7 @@ * Bullet List Item 1 - * Bullet List Item 2 - 1. Numbered List Item 1 - 2. Numbered List Item 2 - * [ ] Check List Item 1 - * [x] Check List Item 2 - * Toggle List Item 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md new file mode 100644 index 0000000000..bb4415d690 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/numberedListStart.md @@ -0,0 +1,2 @@ +5. Item 5 +6. Item 6 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md new file mode 100644 index 0000000000..a01cab2790 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/lists/toggleWithChildren.md @@ -0,0 +1,9 @@ +* Toggle List Item + + Toggle Child 1 + + Toggle Child 2 + +## Toggle Heading + +Heading Child 1 diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/pageBreak/basic.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md index e69de29bb2..8b13789179 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/empty.md @@ -0,0 +1 @@ + diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md new file mode 100644 index 0000000000..8fadfa1d86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/paragraph/multiple.md @@ -0,0 +1,5 @@ +First paragraph + +Second paragraph + +Third paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md new file mode 100644 index 0000000000..83d6a8096d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/basic.md @@ -0,0 +1 @@ +> This is a quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md new file mode 100644 index 0000000000..c2610d0ba7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/multiple.md @@ -0,0 +1,3 @@ +> First quote + +> Second quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md new file mode 100644 index 0000000000..41c50517f2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/nested.md @@ -0,0 +1,3 @@ +> Parent quote + +Nested paragraph diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md new file mode 100644 index 0000000000..71e0af0173 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/styled.md @@ -0,0 +1 @@ +> **Bold** and *italic* quote diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md new file mode 100644 index 0000000000..8510d4defd --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/quote/withLink.md @@ -0,0 +1 @@ +> Quote with [a link](https://www.example.com) diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md new file mode 100644 index 0000000000..3ba8964656 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/backgroundColor.md @@ -0,0 +1 @@ +Highlighted text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md new file mode 100644 index 0000000000..df2474d633 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/bold.md @@ -0,0 +1 @@ +**Bold text** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md new file mode 100644 index 0000000000..1af450cf5e --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/boldItalicStrike.md @@ -0,0 +1 @@ +***~~All styles~~*** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md new file mode 100644 index 0000000000..aa4775ec76 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/code.md @@ -0,0 +1 @@ +`Inline code` diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md new file mode 100644 index 0000000000..b011bd3c15 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/combined.md @@ -0,0 +1 @@ +***Bold and italic*** diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md new file mode 100644 index 0000000000..c6c83dc114 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/italic.md @@ -0,0 +1 @@ +*Italic text* diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md new file mode 100644 index 0000000000..76bd55f326 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/mixedInParagraph.md @@ -0,0 +1 @@ +Normal **bold** *italic* `code `~~strike~~ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md new file mode 100644 index 0000000000..afe555a038 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/strike.md @@ -0,0 +1 @@ +~~Strikethrough text~~ diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md new file mode 100644 index 0000000000..28f332d788 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/textColor.md @@ -0,0 +1 @@ +Colored text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md new file mode 100644 index 0000000000..2ccc77398c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/style/underline.md @@ -0,0 +1 @@ +Underline text diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md new file mode 100644 index 0000000000..53599a4feb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/advancedExample.md @@ -0,0 +1,6 @@ +| This row has headers | This is **RED** | Text is Blue | +| -------------------------------- | --------------- | ------------------ | +| This spans 2 columns\ +and 2 rows | | Sooo many features | +| | | | +| A cell | Another Cell | Aligned center | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md new file mode 100644 index 0000000000..d3d4211640 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/cellTextAlignment.md @@ -0,0 +1,3 @@ +| | | | +| ---------- | ---------- | ---------- | +| Left | Center | Right | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md new file mode 100644 index 0000000000..ba81ea2ca9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/emptyCells.md @@ -0,0 +1,4 @@ +| | | +| ----------- | ---------------- | +| Has content | | +| | Also has content | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md new file mode 100644 index 0000000000..d9ffaf65a3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/hardBreakInCell.md @@ -0,0 +1,4 @@ +| | | +| -------------- | ----------- | +| Line 1\ +Line 2 | Normal cell | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md new file mode 100644 index 0000000000..1c29ad9f94 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/headerRowsAndCols.md @@ -0,0 +1,3 @@ +| Corner | Column Header 1 | Column Header 2 | +| ------------ | --------------- | --------------- | +| Row Header 1 | Data 1 | Data 2 | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md new file mode 100644 index 0000000000..7815cfe199 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/linksInCells.md @@ -0,0 +1,4 @@ +| | | +| ------------------------------------ | ---------------------------- | +| Visit [Example](https://example.com) | Plain cell | +| Data | [Link](https://example2.com) | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md new file mode 100644 index 0000000000..3be705c3e8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/singleCell.md @@ -0,0 +1,3 @@ +| | +| ---------- | +| Only cell | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md new file mode 100644 index 0000000000..ff5eff81a2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/table/styledCellContent.md @@ -0,0 +1,4 @@ +| | | +| ---------- | ---------- | +| **Bold** | *Italic* | +| ~~Strike~~ | `Code` | diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md new file mode 100644 index 0000000000..78cb313b4b --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/markdown/video/withCaption.md @@ -0,0 +1 @@ +
    Video caption
    diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json new file mode 100644 index 0000000000..32e28f6803 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/basic.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "example", + "showPreview": true, + "url": "https://example.com/audio.mp3", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json new file mode 100644 index 0000000000..2149eef7c8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/button.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "url": "", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json new file mode 100644 index 0000000000..a9b3396ea4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/audio/noName.json @@ -0,0 +1,20 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "url": "https://example.com/audio.mp3", + }, + "type": "audio", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json new file mode 100644 index 0000000000..0eb89bbcda --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/complex/document.json @@ -0,0 +1,219 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Document Title", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Introduction paragraph.", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Section 1", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "4", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Text with ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "bold", + "type": "text", + }, + { + "text": " and ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "a link", + "type": "text", + }, + { + "text": ".", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "5", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "First point", + "type": "text", + }, + ], + "type": "bulletListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "6", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Second point", + "type": "text", + }, + ], + "type": "bulletListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "7", + }, + "content": [ + { + "type": "divider", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "8", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "A notable quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "9", + }, + "content": [ + { + "attrs": { + "language": "javascript", + }, + "content": [ + { + "text": "const x = 42;", + "type": "text", + }, + ], + "type": "codeBlock", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json index 174eeecd5b..9db2d101f6 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/between-links.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website2.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json index 4ae3cc342b..7a3369fd10 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/hardbreak/link.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json new file mode 100644 index 0000000000..d147b23ade --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h1.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 1", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json new file mode 100644 index 0000000000..f9f92e7081 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h2.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 2", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json new file mode 100644 index 0000000000..6399a58563 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h3.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 3", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json new file mode 100644 index 0000000000..c23a0c4809 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h4.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 4, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 4", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json new file mode 100644 index 0000000000..0867b7796f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h5.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 5, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 5", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json new file mode 100644 index 0000000000..b5eddefddc --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/h6.json @@ -0,0 +1,26 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 6, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading 6", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json new file mode 100644 index 0000000000..ff107bc15f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/styled.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold ", + "type": "text", + }, + { + "text": "Heading", + "type": "text", + }, + ], + "type": "heading", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json new file mode 100644 index 0000000000..7bb4c57f86 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/heading/toggleable.json @@ -0,0 +1,53 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Heading", + "type": "text", + }, + ], + "type": "heading", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Child content", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json new file mode 100644 index 0000000000..3d56a77d23 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/urlOnly.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "", + "name": "", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "exampleURL", + }, + "type": "image", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json new file mode 100644 index 0000000000..5016f16a10 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/image/withCaption.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "This is a caption", + "name": "Example Image", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json index d546271743..f3020cdf7f 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/adjacent.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -30,10 +27,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website2.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json index 3964520c13..b85306efc8 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/basic.json @@ -15,10 +15,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json new file mode 100644 index 0000000000..dc5aa26397 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/plainUrl.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://www.website.com", + }, + "type": "link", + }, + ], + "text": "https://www.website.com", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json index 84c3a57c95..2c01e6c1a1 100644 --- a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/styled.json @@ -18,10 +18,7 @@ }, { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, @@ -33,10 +30,7 @@ "marks": [ { "attrs": { - "class": null, "href": "https://www.website.com", - "rel": "noopener noreferrer nofollow", - "target": "_blank", }, "type": "link", }, diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json new file mode 100644 index 0000000000..4de7926c89 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/urlWithParens.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://en.wikipedia.org/wiki/Example_(disambiguation)", + }, + "type": "link", + }, + ], + "text": "Example", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json new file mode 100644 index 0000000000..bf1f1da31f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/link/withCode.json @@ -0,0 +1,49 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "See the ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "docs", + "type": "text", + }, + { + "text": " for ", + "type": "text", + }, + { + "marks": [ + { + "type": "code", + }, + ], + "text": "config", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json new file mode 100644 index 0000000000..387b4fa073 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/numberedListStart.json @@ -0,0 +1,48 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "start": 5, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Item 5", + "type": "text", + }, + ], + "type": "numberedListItem", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "start": undefined, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Item 6", + "type": "text", + }, + ], + "type": "numberedListItem", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json new file mode 100644 index 0000000000..9b7407a060 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/lists/toggleWithChildren.json @@ -0,0 +1,124 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle List Item", + "type": "text", + }, + ], + "type": "toggleListItem", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Child 1", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Child 2", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "4", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Toggle Heading", + "type": "text", + }, + ], + "type": "heading", + }, + { + "content": [ + { + "attrs": { + "id": "5", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Heading Child 1", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json new file mode 100644 index 0000000000..affba0772c --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/paragraph/multiple.json @@ -0,0 +1,68 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "First paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Second paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "3", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Third paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json new file mode 100644 index 0000000000..9234a8f05f --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/basic.json @@ -0,0 +1,23 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "This is a quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json new file mode 100644 index 0000000000..458e24879a --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/multiple.json @@ -0,0 +1,44 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "First quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Second quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json new file mode 100644 index 0000000000..b3c46ed220 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/nested.json @@ -0,0 +1,50 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Parent quote", + "type": "text", + }, + ], + "type": "quote", + }, + { + "content": [ + { + "attrs": { + "id": "2", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Nested paragraph", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, + ], + "type": "blockGroup", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json new file mode 100644 index 0000000000..a482c2ad58 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/styled.json @@ -0,0 +1,45 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold ", + "type": "text", + }, + { + "text": "and ", + "type": "text", + }, + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "italic", + "type": "text", + }, + { + "text": " quote", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json new file mode 100644 index 0000000000..c0e2857679 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/quote/withLink.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textColor": "default", + }, + "content": [ + { + "text": "Quote with ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://www.example.com", + }, + "type": "link", + }, + ], + "text": "a link", + "type": "text", + }, + ], + "type": "quote", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json new file mode 100644 index 0000000000..e07954b219 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/backgroundColor.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "stringValue": "blue", + }, + "type": "backgroundColor", + }, + ], + "text": "Highlighted text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json new file mode 100644 index 0000000000..b6d5b7a208 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/bold.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json new file mode 100644 index 0000000000..7c5f05d763 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/boldItalicStrike.json @@ -0,0 +1,35 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + { + "type": "italic", + }, + { + "type": "strike", + }, + ], + "text": "All styles", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json new file mode 100644 index 0000000000..2cf0463e03 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/code.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "code", + }, + ], + "text": "Inline code", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json new file mode 100644 index 0000000000..5b91fb8abb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/combined.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "bold", + }, + { + "type": "italic", + }, + ], + "text": "Bold and italic", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json new file mode 100644 index 0000000000..7d22809e81 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/italic.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "Italic text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json new file mode 100644 index 0000000000..d72aa1d3bb --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/mixedInParagraph.json @@ -0,0 +1,60 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "text": "Normal ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "bold ", + "type": "text", + }, + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "italic ", + "type": "text", + }, + { + "marks": [ + { + "type": "code", + }, + ], + "text": "code ", + "type": "text", + }, + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "strike", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json new file mode 100644 index 0000000000..756569ade9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/strike.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "Strikethrough text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json new file mode 100644 index 0000000000..7bca812fd7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/textColor.json @@ -0,0 +1,32 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "attrs": { + "stringValue": "red", + }, + "type": "textColor", + }, + ], + "text": "Colored text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json new file mode 100644 index 0000000000..348c2ae742 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/style/underline.json @@ -0,0 +1,29 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "marks": [ + { + "type": "underline", + }, + ], + "text": "Underline text", + "type": "text", + }, + ], + "type": "paragraph", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json new file mode 100644 index 0000000000..1fdde23a6b --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/advancedExample.json @@ -0,0 +1,265 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 199, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This row has headers", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "red", + "colspan": 1, + "colwidth": [ + 148, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This is ", + "type": "text", + }, + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "RED", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "blue", + }, + "content": [ + { + "content": [ + { + "text": "Text is Blue", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "yellow", + "colspan": 2, + "colwidth": [ + 199, + 148, + ], + "rowspan": 2, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "This spans 2 columns", + "type": "text", + }, + { + "type": "hardBreak", + }, + { + "text": "and 2 rows", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "gray", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Sooo many features", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "gray", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "purple", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 199, + ], + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "A cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 148, + ], + "rowspan": 1, + "textAlignment": "right", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Another Cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": [ + 201, + ], + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Aligned center", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json new file mode 100644 index 0000000000..c7ded0cfc6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/cellTextAlignment.json @@ -0,0 +1,89 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Left", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "center", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Center", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "right", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Right", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json new file mode 100644 index 0000000000..05bb8aa0af --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/emptyCells.json @@ -0,0 +1,104 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Has content", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Also has content", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json new file mode 100644 index 0000000000..121834448d --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/hardBreakInCell.json @@ -0,0 +1,74 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Line 1", + "type": "text", + }, + { + "type": "hardBreak", + }, + { + "text": "Line 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Normal cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json new file mode 100644 index 0000000000..606ec05c49 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/headerRowsAndCols.json @@ -0,0 +1,160 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Corner", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Column Header 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Column Header 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Row Header 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableHeader", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data 1", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data 2", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json new file mode 100644 index 0000000000..6f491b4f48 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/linksInCells.json @@ -0,0 +1,136 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Visit ", + "type": "text", + }, + { + "marks": [ + { + "attrs": { + "href": "https://example.com", + }, + "type": "link", + }, + ], + "text": "Example", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Plain cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Data", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "attrs": { + "href": "https://example2.com", + }, + "type": "link", + }, + ], + "text": "Link", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json new file mode 100644 index 0000000000..dd4628d177 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/singleCell.json @@ -0,0 +1,45 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "text": "Only cell", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json new file mode 100644 index 0000000000..3727fa1cf0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/table/styledCellContent.json @@ -0,0 +1,136 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "bold", + }, + ], + "text": "Bold", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "italic", + }, + ], + "text": "Italic", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + { + "content": [ + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "strike", + }, + ], + "text": "Strike", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + { + "attrs": { + "backgroundColor": "default", + "colspan": 1, + "colwidth": null, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "content": [ + { + "content": [ + { + "marks": [ + { + "type": "code", + }, + ], + "text": "Code", + "type": "text", + }, + ], + "type": "tableParagraph", + }, + ], + "type": "tableCell", + }, + ], + "type": "tableRow", + }, + ], + "type": "table", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json new file mode 100644 index 0000000000..6d6c134eb0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/export/__snapshots__/nodes/video/withCaption.json @@ -0,0 +1,22 @@ +[ + { + "attrs": { + "id": "1", + }, + "content": [ + { + "attrs": { + "backgroundColor": "default", + "caption": "Video caption", + "name": "Example Video", + "previewWidth": undefined, + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/video.mp4", + }, + "type": "video", + }, + ], + "type": "blockContainer", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts index 899331b55d..bd8cb77493 100644 --- a/tests/src/unit/core/formatConversion/export/exportTestInstances.ts +++ b/tests/src/unit/core/formatConversion/export/exportTestInstances.ts @@ -159,6 +159,42 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "lists/toggleWithChildren", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + children: [ + { + type: "paragraph", + content: "Toggle Child 1", + }, + { + type: "paragraph", + content: "Toggle Child 2", + }, + ], + }, + { + type: "heading", + props: { + level: 2, + isToggleable: true, + }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Heading Child 1", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "lists/nested", @@ -200,54 +236,701 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, - ], - }, - executeTest: testExportBlockNoteHTML, - }, - { - testCase: { - name: "codeBlock/empty", - content: [ + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/empty", + content: [ + { + type: "codeBlock", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/defaultLanguage", + content: [ + { + type: "codeBlock", + content: "console.log('Hello, world!');", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/python", + content: [ + { + type: "codeBlock", + props: { language: "python" }, + content: "print('Hello, world!')", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "codeBlock/contains-newlines", + content: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const hello = 'world';\nconsole.log(hello);\n", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Heading levels + { + testCase: { + name: "heading/h1", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Heading 1", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h2", + content: [ + { + type: "heading", + props: { level: 2 }, + content: "Heading 2", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h3", + content: [ + { + type: "heading", + props: { level: 3 }, + content: "Heading 3", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h4", + content: [ + { + type: "heading", + props: { level: 4 }, + content: "Heading 4", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h5", + content: [ + { + type: "heading", + props: { level: 5 }, + content: "Heading 5", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/h6", + content: [ + { + type: "heading", + props: { level: 6 }, + content: "Heading 6", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/styled", + content: [ + { + type: "heading", + props: { level: 1 }, + content: [ + { + type: "text", + text: "Bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "Heading", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "heading/toggleable", + content: [ + { + type: "heading", + props: { level: 2, isToggleable: true }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Child content", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Quote / Blockquote + { + testCase: { + name: "quote/basic", + content: [ + { + type: "quote", + content: "This is a quote", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/styled", + content: [ + { + type: "quote", + content: [ + { + type: "text", + text: "Bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "and ", + styles: {}, + }, + { + type: "text", + text: "italic", + styles: { italic: true }, + }, + { + type: "text", + text: " quote", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/withLink", + content: [ + { + type: "quote", + content: [ + { + type: "text", + text: "Quote with ", + styles: {}, + }, + { + type: "link", + href: "https://www.example.com", + content: "a link", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/nested", + content: [ + { + type: "quote", + content: "Parent quote", + children: [ + { + type: "paragraph", + content: "Nested paragraph", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "quote/multiple", + content: [ + { + type: "quote", + content: "First quote", + }, + { + type: "quote", + content: "Second quote", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Audio + { + testCase: { + name: "audio/basic", + content: [ + { + type: "audio", + props: { + url: "https://example.com/audio.mp3", + name: "example", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "audio/button", + content: [ + { + type: "audio", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "audio/noName", + content: [ + { + type: "audio", + props: { + url: "https://example.com/audio.mp3", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Individual styles + { + testCase: { + name: "style/bold", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold text", + styles: { bold: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/italic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Italic text", + styles: { italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/underline", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Underline text", + styles: { underline: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/strike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Strikethrough text", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/code", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Inline code", + styles: { code: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/textColor", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Colored text", + styles: { textColor: "red" }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/backgroundColor", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Highlighted text", + styles: { backgroundColor: "blue" }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/combined", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold and italic", + styles: { bold: true, italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/boldItalicStrike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "All styles", + styles: { bold: true, italic: true, strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "style/mixedInParagraph", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Normal ", + styles: {}, + }, + { + type: "text", + text: "bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "italic ", + styles: { italic: true }, + }, + { + type: "text", + text: "code ", + styles: { code: true }, + }, + { + type: "text", + text: "strike", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Numbered list with custom start + { + testCase: { + name: "lists/numberedListStart", + content: [ + { + type: "numberedListItem", + props: { start: 5 }, + content: "Item 5", + }, + { + type: "numberedListItem", + content: "Item 6", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Multiple paragraphs + { + testCase: { + name: "paragraph/multiple", + content: [ + { + type: "paragraph", + content: "First paragraph", + }, + { + type: "paragraph", + content: "Second paragraph", + }, + { + type: "paragraph", + content: "Third paragraph", + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Mixed block types document + { + testCase: { + name: "complex/document", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Document Title", + }, + { + type: "paragraph", + content: "Introduction paragraph.", + }, + { + type: "heading", + props: { level: 2 }, + content: "Section 1", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Text with ", + styles: {}, + }, + { + type: "text", + text: "bold", + styles: { bold: true }, + }, + { + type: "text", + text: " and ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "a link", + }, + { + type: "text", + text: ".", + styles: {}, + }, + ], + }, + { + type: "bulletListItem", + content: "First point", + }, + { + type: "bulletListItem", + content: "Second point", + }, + { + type: "divider", + }, + { + type: "quote", + content: "A notable quote", + }, { type: "codeBlock", + props: { language: "javascript" }, + content: "const x = 42;", }, ], }, executeTest: testExportBlockNoteHTML, }, + // Link with inline code { testCase: { - name: "codeBlock/defaultLanguage", + name: "link/withCode", content: [ { - type: "codeBlock", - content: "console.log('Hello, world!');", + type: "paragraph", + content: [ + { + type: "text", + text: "See the ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "docs", + }, + { + type: "text", + text: " for ", + styles: {}, + }, + { + type: "text", + text: "config", + styles: { code: true }, + }, + ], }, ], }, executeTest: testExportBlockNoteHTML, }, + // Image with caption { testCase: { - name: "codeBlock/python", + name: "image/withCaption", content: [ { - type: "codeBlock", - props: { language: "python" }, - content: "print('Hello, world!')", + type: "image", + props: { + url: "https://example.com/image.png", + name: "Example Image", + caption: "This is a caption", + }, }, ], }, executeTest: testExportBlockNoteHTML, }, + // Video with caption { testCase: { - name: "codeBlock/contains-newlines", + name: "video/withCaption", content: [ { - type: "codeBlock", - props: { language: "javascript" }, - content: "const hello = 'world';\nconsole.log(hello);\n", + type: "video", + props: { + url: "https://example.com/video.mp4", + name: "Example Video", + caption: "Video caption", + }, }, ], }, @@ -418,6 +1101,23 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + // Image with only a URL — no name, no caption. Confirms markdown export + // stays as plain `![](url)` without wrapping in a `
    ` (the figure + // form is only used to carry caption text through the round-trip). + testCase: { + name: "image/urlOnly", + content: [ + { + type: "image", + props: { + url: "exampleURL", + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "image/noPreview", @@ -455,8 +1155,263 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< caption: "Caption", previewWidth: 256, }, - }, - ], + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/basic", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/allColWidths", + content: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, 200, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + ], + }, }, ], }, @@ -464,12 +1419,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/basic", + name: "table/mixedColWidths", content: [ { type: "table", content: { type: "tableContent", + columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -591,14 +1547,51 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/allColWidths", + name: "table/mixedCellColors", content: [ { type: "table", content: { type: "tableContent", - columnWidths: [100, 200, 300], + columnWidths: [100, undefined, 300], rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "blue", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, { cells: [ { @@ -673,6 +1666,49 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "table/mixedRowspansAndColspans", + content: [ + { + type: "table", + content: { + type: "tableContent", + columnWidths: [100, 200, 300], + rows: [ + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "red", + colspan: 2, + rowspan: 1, + textAlignment: "left", + textColor: "blue", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "yellow", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "red", + }, + }, + ], + }, { cells: [ { @@ -681,11 +1717,26 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< props: { backgroundColor: "default", colspan: 1, + rowspan: 2, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 2, rowspan: 1, textAlignment: "left", textColor: "default", }, }, + ], + }, + { + cells: [ { type: "tableCell", content: ["Table Cell"], @@ -719,13 +1770,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/mixedColWidths", + name: "table/headerRows", content: [ { type: "table", content: { + headerRows: 1, type: "tableContent", - columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -847,13 +1898,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { testCase: { - name: "table/mixedCellColors", + name: "table/headerCols", content: [ { type: "table", content: { + headerCols: 1, type: "tableContent", - columnWidths: [100, undefined, 300], rows: [ { cells: [ @@ -861,18 +1912,18 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "red", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", - textColor: "blue", + textColor: "default", }, }, { type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "blue", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", @@ -883,20 +1934,113 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< type: "tableCell", content: ["Table Cell"], props: { - backgroundColor: "yellow", + backgroundColor: "default", colspan: 1, rowspan: 1, textAlignment: "left", - textColor: "red", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", + }, + }, + { + type: "tableCell", + content: ["Table Cell"], + props: { + backgroundColor: "default", + colspan: 1, + rowspan: 1, + textAlignment: "left", + textColor: "default", }, }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: header rows + header cols together + { + testCase: { + name: "table/headerRowsAndCols", + content: [ + { + type: "table", + content: { + type: "tableContent", + headerRows: 1, + headerCols: 1, + rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Corner"], props: { backgroundColor: "default", colspan: 1, @@ -907,7 +2051,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Column Header 1"], props: { backgroundColor: "default", colspan: 1, @@ -918,7 +2062,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Column Header 2"], props: { backgroundColor: "default", colspan: 1, @@ -933,7 +2077,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Row Header 1"], props: { backgroundColor: "default", colspan: 1, @@ -944,7 +2088,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Data 1"], props: { backgroundColor: "default", colspan: 1, @@ -955,7 +2099,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Data 2"], props: { backgroundColor: "default", colspan: 1, @@ -973,61 +2117,47 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: styled content in cells { testCase: { - name: "table/mixedRowspansAndColspans", + name: "table/styledCellContent", content: [ { type: "table", content: { type: "tableContent", - columnWidths: [100, 200, 300], rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], - props: { - backgroundColor: "red", - colspan: 2, - rowspan: 1, - textAlignment: "left", - textColor: "blue", - }, - }, - { - type: "tableCell", - content: ["Table Cell"], - props: { - backgroundColor: "yellow", - colspan: 1, - rowspan: 1, - textAlignment: "left", - textColor: "red", - }, - }, - ], - }, - { - cells: [ - { - type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Bold", + styles: { bold: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, - rowspan: 2, + rowspan: 1, textAlignment: "left", textColor: "default", }, }, { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Italic", + styles: { italic: true }, + }, + ], props: { backgroundColor: "default", - colspan: 2, + colspan: 1, rowspan: 1, textAlignment: "left", textColor: "default", @@ -1039,7 +2169,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Strike", + styles: { strike: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1050,7 +2186,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Code", + styles: { code: true }, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1068,21 +2210,32 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: links in cells { testCase: { - name: "table/headerRows", + name: "table/linksInCells", content: [ { type: "table", content: { - headerRows: 1, type: "tableContent", rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "text", + text: "Visit ", + styles: {}, + }, + { + type: "link", + href: "https://example.com", + content: "Example", + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1093,7 +2246,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Plain cell"], props: { backgroundColor: "default", colspan: 1, @@ -1102,9 +2255,13 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Data"], props: { backgroundColor: "default", colspan: 1, @@ -1113,13 +2270,15 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, - ], - }, - { - cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [ + { + type: "link", + href: "https://example2.com", + content: "Link", + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1128,9 +2287,30 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: empty cells + { + testCase: { + name: "table/emptyCells", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Has content"], props: { backgroundColor: "default", colspan: 1, @@ -1141,7 +2321,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: [], props: { backgroundColor: "default", colspan: 1, @@ -1156,7 +2336,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [], props: { backgroundColor: "default", colspan: 1, @@ -1167,7 +2347,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Also has content"], props: { backgroundColor: "default", colspan: 1, @@ -1176,9 +2356,30 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< textColor: "default", }, }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: single cell + { + testCase: { + name: "table/singleCell", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Only cell"], props: { backgroundColor: "default", colspan: 1, @@ -1196,49 +2397,103 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + // Advanced table: from the advanced-tables example (large merged cells) { testCase: { - name: "table/headerCols", + name: "table/advancedExample", content: [ { type: "table", content: { - headerCols: 1, type: "tableContent", + columnWidths: [199, 148, 201], + headerRows: 1, rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["This row has headers"], 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, - textAlignment: "left", + backgroundColor: "red", textColor: "default", + textAlignment: "center", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Text is Blue"], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "default", + textColor: "blue", + textAlignment: "center", + }, + }, + ], + }, + { + cells: [ + { + type: "tableCell", + content: ["This spans 2 columns\nand 2 rows"], + props: { + colspan: 2, + rowspan: 2, + backgroundColor: "yellow", + textColor: "default", textAlignment: "left", + }, + }, + { + type: "tableCell", + content: ["Sooo many features"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "gray", textColor: "default", + textAlignment: "left", }, }, + ], + }, + { + cells: [ { type: "tableCell", - content: ["Table Cell"], + content: [], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "gray", + textColor: "purple", textAlignment: "left", - textColor: "default", }, }, ], @@ -1247,18 +2502,67 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["A cell"], props: { - backgroundColor: "default", colspan: 1, rowspan: 1, + backgroundColor: "default", + textColor: "default", textAlignment: "left", + }, + }, + { + type: "tableCell", + content: ["Another Cell"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", textColor: "default", + textAlignment: "right", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Aligned center"], + props: { + colspan: 1, + rowspan: 1, + backgroundColor: "default", + textColor: "default", + textAlignment: "center", + }, + }, + ], + }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: hard breaks in cells + { + testCase: { + name: "table/hardBreakInCell", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: [ + { + type: "tableCell", + content: [ + { + type: "text", + text: "Line 1\nLine 2", + styles: {}, + }, + ], props: { backgroundColor: "default", colspan: 1, @@ -1269,7 +2573,7 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Normal cell"], props: { backgroundColor: "default", colspan: 1, @@ -1280,11 +2584,28 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, ], }, + ], + }, + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + // Advanced table: mixed text alignment per cell + { + testCase: { + name: "table/cellTextAlignment", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ { cells: [ { type: "tableCell", - content: ["Table Cell"], + content: ["Left"], props: { backgroundColor: "default", colspan: 1, @@ -1295,23 +2616,23 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, { type: "tableCell", - content: ["Table Cell"], + content: ["Center"], props: { backgroundColor: "default", colspan: 1, rowspan: 1, - textAlignment: "left", + textAlignment: "center", textColor: "default", }, }, { type: "tableCell", - content: ["Table Cell"], + content: ["Right"], props: { backgroundColor: "default", colspan: 1, rowspan: 1, - textAlignment: "left", + textAlignment: "right", textColor: "default", }, }, @@ -1399,6 +2720,44 @@ export const exportTestInstancesBlockNoteHTML: TestInstance< }, executeTest: testExportBlockNoteHTML, }, + { + testCase: { + name: "link/plainUrl", + content: [ + { + // id: UniqueID.options.generateID(), + type: "paragraph", + content: [ + { + type: "link", + href: "https://www.website.com", + content: "https://www.website.com", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, + { + testCase: { + name: "link/urlWithParens", + content: [ + { + // id: UniqueID.options.generateID(), + type: "paragraph", + content: [ + { + type: "link", + href: "https://en.wikipedia.org/wiki/Example_(disambiguation)", + content: "Example", + }, + ], + }, + ], + }, + executeTest: testExportBlockNoteHTML, + }, { testCase: { name: "hardbreak/basic", diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json new file mode 100644 index 0000000000..0569abc8a9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bold.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json new file mode 100644 index 0000000000..f6b0ca8045 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/boldItalic.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "Bold and italic", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json new file mode 100644 index 0000000000..b5999d3ec9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/bulletList.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json new file mode 100644 index 0000000000..3f344bf122 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/checkList.json @@ -0,0 +1,38 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Unchecked", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json new file mode 100644 index 0000000000..63ebc503a5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/codeBlock.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 42;", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json new file mode 100644 index 0000000000..570c7e98d5 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/complexDocument.json @@ -0,0 +1,106 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Title", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " text.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet 1", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet 2", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": undefined, + "id": "5", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "print('hello')", + "type": "text", + }, + ], + "id": "6", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json new file mode 100644 index 0000000000..f81fde4a33 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/deeplyNestedLists.json @@ -0,0 +1,144 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 4 numbered", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 3 bullet", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 2 numbered", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 2 sibling", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 1 bullet", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Deep checklist item", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Child of second bullet", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Another top-level bullet", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json new file mode 100644 index 0000000000..69bef1f3f3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/defaultBlocks.json @@ -0,0 +1,474 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Welcome to this demo!", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Blocks:", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Heading", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet List Item", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Numbered List Item", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check List Item", + "type": "text", + }, + ], + "id": "9", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle List Item", + "type": "text", + }, + ], + "id": "10", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "console.log('Hello, world!');", + "type": "text", + }, + ], + "id": "11", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Table Cell", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "12", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": undefined, + "id": "13", + "props": { + "backgroundColor": "default", + "caption": "From https://placehold.co/332x322.jpg", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://placehold.co/332x322.jpg", + }, + "type": "image", + }, + { + "children": [], + "content": undefined, + "id": "14", + "props": { + "backgroundColor": "default", + "caption": "From https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", + }, + "type": "video", + }, + { + "children": [], + "content": undefined, + "id": "15", + "props": { + "backgroundColor": "default", + "caption": "From https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + "name": "", + "showPreview": true, + "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-audio/t-rex-roar.mp3", + }, + "type": "audio", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Inline Content:", + "type": "text", + }, + ], + "id": "16", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "Styled Text", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://www.blocknotejs.org", + "type": "link", + }, + ], + "id": "17", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json new file mode 100644 index 0000000000..e944763aea --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/divider.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Before", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "After", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json new file mode 100644 index 0000000000..5626739e88 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/hardBreak.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line 1 + Line 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json new file mode 100644 index 0000000000..f1ce124a58 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/headingLevels.json @@ -0,0 +1,59 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 3", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json new file mode 100644 index 0000000000..c9a6bddd61 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/image.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Example", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json new file mode 100644 index 0000000000..6ddd0e59ee --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/inlineCode.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "code": true, + }, + "text": "Code text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json new file mode 100644 index 0000000000..01ec89cd69 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/italic.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json new file mode 100644 index 0000000000..fabbb2daa3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/link.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " more text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json new file mode 100644 index 0000000000..7085f4c8b8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/mixedStyles.json @@ -0,0 +1,50 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Normal ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json new file mode 100644 index 0000000000..fc70b307a0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/multipleParagraphs.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json new file mode 100644 index 0000000000..90bb306cb8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/nestedLists.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json new file mode 100644 index 0000000000..f2dfd7912e --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/numberedList.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json new file mode 100644 index 0000000000..575bc9876a --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/paragraph.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Simple paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json new file mode 100644 index 0000000000..7a2b3b4601 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/quote.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A quote", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json new file mode 100644 index 0000000000..0ede1c2000 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/specialCharEscaping.json @@ -0,0 +1,124 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Literal ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "asterisks", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "double asterisks", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Backticks ` in plain text and ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "double", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Underscores ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "here", + "type": "text", + }, + { + "styles": {}, + "text": " and ~tildes~ and [brackets]", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Pipes | and backslash \ and #hash at start", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = `template ${literal}`; +const y = '```triple backticks```';", + "type": "text", + }, + ], + "id": "5", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json new file mode 100644 index 0000000000..7f504a4b3f --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/strike.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json new file mode 100644 index 0000000000..7fb20ea004 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/table.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": undefined, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json new file mode 100644 index 0000000000..7944f66a18 --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/tableWithHeaderRow.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json new file mode 100644 index 0000000000..57607adfdf --- /dev/null +++ b/tests/src/unit/core/formatConversion/exportParseEquality/__snapshots__/markdown/markdown/video.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Example", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/video.mp4", + }, + "type": "video", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts index 33d9b09c15..b9dcba4828 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts +++ b/tests/src/unit/core/formatConversion/exportParseEquality/exportParseEqualityTestInstances.ts @@ -2,6 +2,7 @@ import { ExportParseEqualityTestCase } from "../../../shared/formatConversion/ex import { testExportParseEqualityBlockNoteHTML, testExportParseEqualityHTML, + testExportParseEqualityMarkdown, } from "../../../shared/formatConversion/exportParseEquality/exportParseEqualityTestExecutors.js"; import { TestInstance } from "../../../types.js"; import { @@ -203,6 +204,62 @@ export const exportParseEqualityTestInstancesHTML: TestInstance< }, executeTest: testExportParseEqualityHTML, }, + { + testCase: { + name: "lists/toggleListItem", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, + { + testCase: { + name: "lists/toggleListItemWithChildren", + content: [ + { + type: "toggleListItem", + content: "Toggle List Item", + children: [ + { + type: "paragraph", + content: "Child 1", + }, + { + type: "paragraph", + content: "Child 2", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, + { + testCase: { + name: "lists/toggleHeading", + content: [ + { + type: "heading", + props: { + level: 2, + isToggleable: true, + }, + content: "Toggle Heading", + children: [ + { + type: "paragraph", + content: "Heading Child 1", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityHTML, + }, { testCase: { name: "tables/advanced", @@ -315,3 +372,723 @@ export const exportParseEqualityTestInstancesHTML: TestInstance< executeTest: testExportParseEqualityHTML, }, ]; + +// Markdown round-trip tests: blocks → markdown → blocks +// Markdown is a lossy format (no colors, underline, alignment), so these tests +// use snapshot matching to capture the expected round-trip result rather than +// strict equality with the input. This is critical for verifying that the +// custom markdown parser/serializer produces the same round-trip results. +export const exportParseEqualityTestInstancesMarkdown: TestInstance< + ExportParseEqualityTestCase< + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema + >, + TestBlockSchema, + TestInlineContentSchema, + TestStyleSchema +>[] = [ + { + testCase: { + name: "markdown/paragraph", + content: [ + { + type: "paragraph", + content: "Simple paragraph", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/multipleParagraphs", + content: [ + { + type: "paragraph", + content: "First paragraph", + }, + { + type: "paragraph", + content: "Second paragraph", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/headingLevels", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Heading 1", + }, + { + type: "heading", + props: { level: 2 }, + content: "Heading 2", + }, + { + type: "heading", + props: { level: 3 }, + content: "Heading 3", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/bulletList", + content: [ + { + type: "bulletListItem", + content: "Item 1", + }, + { + type: "bulletListItem", + content: "Item 2", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/numberedList", + content: [ + { + type: "numberedListItem", + content: "Item 1", + }, + { + type: "numberedListItem", + content: "Item 2", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/checkList", + content: [ + { + type: "checkListItem", + content: "Unchecked", + }, + { + type: "checkListItem", + props: { checked: true }, + content: "Checked", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/nestedLists", + content: [ + { + type: "bulletListItem", + content: "Parent", + children: [ + { + type: "numberedListItem", + content: "Child 1", + }, + { + type: "numberedListItem", + content: "Child 2", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/codeBlock", + content: [ + { + type: "codeBlock", + props: { language: "javascript" }, + content: "const x = 42;", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/divider", + content: [ + { + type: "paragraph", + content: "Before", + }, + { + type: "divider", + }, + { + type: "paragraph", + content: "After", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/bold", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold text", + styles: { bold: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/italic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Italic text", + styles: { italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/strike", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Strikethrough text", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/inlineCode", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Code text", + styles: { code: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/boldItalic", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Bold and italic", + styles: { bold: true, italic: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/link", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Text ", + styles: {}, + }, + { + type: "link", + content: "Link", + href: "https://example.com", + }, + { + type: "text", + text: " more text", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/image", + content: [ + { + type: "image", + props: { + url: "https://example.com/image.png", + name: "Example", + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/video", + content: [ + { + type: "video", + props: { + url: "https://example.com/video.mp4", + name: "Example", + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/table", + content: [ + { + type: "table", + content: { + type: "tableContent", + rows: [ + { + cells: ["Header 1", "Header 2"], + }, + { + cells: ["Cell 1", "Cell 2"], + }, + ], + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + // Complementary check for https://github.com/TypeCellOS/BlockNote/issues/739: + // a table WITH a real header row must round-trip with the header preserved + // (i.e. non-empty headers must not be treated as the empty-header case). + testCase: { + name: "markdown/tableWithHeaderRow", + content: [ + { + type: "table", + content: { + type: "tableContent", + headerRows: 1, + rows: [ + { + cells: ["Header 1", "Header 2"], + }, + { + cells: ["Cell 1", "Cell 2"], + }, + ], + }, + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/quote", + content: [ + { + type: "quote", + content: "A quote", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/hardBreak", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Line 1\nLine 2", + styles: {}, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/mixedStyles", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Normal ", + styles: {}, + }, + { + type: "text", + text: "bold ", + styles: { bold: true }, + }, + { + type: "text", + text: "italic ", + styles: { italic: true }, + }, + { + type: "text", + text: "strike", + styles: { strike: true }, + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/complexDocument", + content: [ + { + type: "heading", + props: { level: 1 }, + content: "Title", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Paragraph with ", + styles: {}, + }, + { + type: "text", + text: "bold", + styles: { bold: true }, + }, + { + type: "text", + text: " text.", + styles: {}, + }, + ], + }, + { + type: "bulletListItem", + content: "Bullet 1", + }, + { + type: "bulletListItem", + content: "Bullet 2", + }, + { + type: "divider", + }, + { + type: "codeBlock", + props: { language: "python" }, + content: "print('hello')", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/deeplyNestedLists", + content: [ + { + type: "bulletListItem", + content: "Level 1 bullet", + children: [ + { + type: "numberedListItem", + content: "Level 2 numbered", + children: [ + { + type: "bulletListItem", + content: "Level 3 bullet", + children: [ + { + type: "numberedListItem", + content: "Level 4 numbered", + }, + ], + }, + ], + }, + { + type: "numberedListItem", + content: "Level 2 sibling", + }, + ], + }, + { + type: "bulletListItem", + content: "Another top-level bullet", + children: [ + { + type: "bulletListItem", + content: "Child of second bullet", + children: [ + { + type: "checkListItem", + content: "Deep checklist item", + }, + ], + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + testCase: { + name: "markdown/specialCharEscaping", + content: [ + { + type: "paragraph", + content: [ + { + type: "text", + text: "Literal *asterisks* and **double asterisks**", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Backticks ` in plain text and `` double ``", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Underscores _here_ and ~tildes~ and [brackets]", + styles: {}, + }, + ], + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Pipes | and backslash \\ and #hash at start", + styles: {}, + }, + ], + }, + { + type: "codeBlock", + props: { language: "" }, + // eslint-disable-next-line no-template-curly-in-string + content: "const x = `template ${literal}`;\nconst y = '```triple backticks```';", + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, + { + // Mirrors the default-blocks demo (examples/01-basic/04-default-blocks) + // so we get a single round-trip snapshot covering every default block + // type. Markdown is lossy (colors/alignment/file blocks/toggle + // affordances are dropped), so the snapshot documents what survives. + // Image/video/audio captions are preserved via raw `
    ` HTML. + testCase: { + name: "markdown/defaultBlocks", + content: [ + { + type: "paragraph", + content: "Welcome to this demo!", + }, + { + type: "paragraph", + content: [ + { + type: "text", + text: "Blocks:", + styles: { bold: true }, + }, + ], + }, + { + type: "paragraph", + content: "Paragraph", + }, + { + type: "heading", + content: "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", + }, + { + 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", + 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", + }, + ], + }, + ], + }, + executeTest: testExportParseEqualityMarkdown, + }, +]; diff --git a/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts b/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts index fc7f33f3c8..bcbad6fea9 100644 --- a/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts +++ b/tests/src/unit/core/formatConversion/exportParseEquality/runTests.test.ts @@ -5,6 +5,7 @@ import { testSchema } from "../../testSchema.js"; import { exportParseEqualityTestInstancesBlockNoteHTML, exportParseEqualityTestInstancesHTML, + exportParseEqualityTestInstancesMarkdown, } from "./exportParseEqualityTestInstances.js"; // Tests for verifying that exporting blocks to another format, then importing @@ -36,3 +37,16 @@ describe("Export/parse equality tests (HTML)", () => { }); } }); + +describe("Export/parse equality tests (Markdown)", () => { + const getEditor = createTestEditor(testSchema); + + for (const { + testCase, + executeTest, + } of exportParseEqualityTestInstancesMarkdown) { + it(`${testCase.name}`, async () => { + await executeTest(getEditor(), testCase); + }); + } +}); diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json index 40018a5ae2..0ee4579333 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedTextTableCell.json @@ -15,10 +15,7 @@ { "styles": {}, "text": "Table Cell -Table Cell - - Table Cell -", +Table Cell Table Cell", "type": "text", }, ], diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json new file mode 100644 index 0000000000..93b62ea578 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/mixedToggleAndBulletList.json @@ -0,0 +1,71 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Bullet Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Child", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another Bullet", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json new file mode 100644 index 0000000000..b7eb0a7de4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/msWordPaste.json @@ -0,0 +1,126 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "underline": true, + }, + "text": "Que se passe-t-il si je réponds tard à un message chat et que l'utilisateur n'est plus en ligne :", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Lorsque vous envoyez un message à un utilisateur dans une conversation chat, et qu'il est encore en ligne, il recevra le message sur sa bulle chatbot.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Cependant S'il n'est plus en ligne, votre message sera envoyé par email si :", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": ". l'utilisateur n'a pas lu votre réponse après 2 minutes", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": ". l'utilisateur n'est plus présent sur votre site web", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": " ", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Cela se fait automatiquement donc, lorsque nous répondons par chat, si l'utilisateur n'est plus là, Crisp renvoie le message alors par email et le canal de discussion se transforme en canal de discussion email. + + Il est possible aussi de créer une conversation email directement le profil de l'utilisateur (bouton bleu en haut à droite de la conversation)", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json index 7502bc6d5a..9f1da7de75 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/notion.json @@ -406,11 +406,11 @@ With Hard Break", "content": [ { "styles": {}, - "text": "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + "text": "https://placehold.co/800x540.png", "type": "text", }, ], - "href": "https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg", + "href": "https://placehold.co/800x540.png", "type": "link", }, ], diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json new file mode 100644 index 0000000000..5f0b0e6bb0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/standaloneDetailsSummary.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child paragraph 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child paragraph 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json new file mode 100644 index 0000000000..4186324717 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeading.json @@ -0,0 +1,39 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Heading", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": true, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json new file mode 100644 index 0000000000..4661a0b906 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleHeadingWithoutChildren.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Heading No Children", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": true, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json new file mode 100644 index 0000000000..ab85ec82d4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItem.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Toggle Item 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json new file mode 100644 index 0000000000..f3fdda6f01 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithChildren.json @@ -0,0 +1,54 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child 2", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle Item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json new file mode 100644 index 0000000000..1e47b38cf8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/html/toggleListItemWithImage.json @@ -0,0 +1,51 @@ +[ + { + "children": [ + { + "children": [], + "content": undefined, + "id": "2", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "", + "showPreview": true, + "textAlignment": "left", + "url": "http://localhost:3000/exampleURL", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text after image", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + ], + "content": [ + { + "styles": {}, + "text": "Toggle with image", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "toggleListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json new file mode 100644 index 0000000000..24ea75b41d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentFormattedRuns.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json new file mode 100644 index 0000000000..bdaf169421 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/adjacentLinks.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Link1", + "type": "text", + }, + ], + "href": "https://example1.com", + "type": "link", + }, + { + "content": [ + { + "styles": {}, + "text": "Link2", + "type": "text", + }, + ], + "href": "https://example2.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json new file mode 100644 index 0000000000..fbdb14c852 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/backslashEscapes.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "*not bold* [not a link] ~not strike~", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json new file mode 100644 index 0000000000..b50ace7d9e --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/bareAngleBrackets.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "1 < 2 and 3 > 0", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json new file mode 100644 index 0000000000..dde187fb49 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlComment.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Next paragraph.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json new file mode 100644 index 0000000000..76c9d076e7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlDiv.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A warning block.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json new file mode 100644 index 0000000000..f0b3a0131d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockHtmlInterruptsParagraph.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Some text before.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "raw block Some text after.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json new file mode 100644 index 0000000000..146a54b49b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteLazyContinuation.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "This is a quote + that continues here + and here too", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json new file mode 100644 index 0000000000..669e44db40 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteMultiline.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json new file mode 100644 index 0000000000..b4a209b413 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithCode.json @@ -0,0 +1,30 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote with ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "inline code", + "type": "text", + }, + { + "styles": {}, + "text": " inside", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json new file mode 100644 index 0000000000..8cd0e17218 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/blockquoteWithLink.json @@ -0,0 +1,34 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Quote with ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "a link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " inside", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json new file mode 100644 index 0000000000..0569abc8a9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json new file mode 100644 index 0000000000..fc81db25f4 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/boldUnderscore.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold with underscores", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json new file mode 100644 index 0000000000..62136bf1c9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListBasic.json @@ -0,0 +1,56 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Unchecked item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another unchecked", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json new file mode 100644 index 0000000000..4084a42458 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListMixed.json @@ -0,0 +1,55 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Regular bullet", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Checked item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json new file mode 100644 index 0000000000..ff5c84bf29 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/checkListNested.json @@ -0,0 +1,57 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child checked", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "checked": true, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Child unchecked", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Parent item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json new file mode 100644 index 0000000000..cf59869a6f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockBasic.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "console.log('Hello');", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json new file mode 100644 index 0000000000..475525d84e --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockIndented.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 1;", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "ts", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json new file mode 100644 index 0000000000..78cd6179bf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockPython.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "def hello(): + print("Hello, world!")", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json new file mode 100644 index 0000000000..1a656bd726 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockTildes.json @@ -0,0 +1,17 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "code with tildes", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "text", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json new file mode 100644 index 0000000000..90eb554680 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithLanguage.json @@ -0,0 +1,18 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "const x = 42; +console.log(x);", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json new file mode 100644 index 0000000000..bdf7b585f8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeBlockWithSpecialChars.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "
    +

    Hello **not bold**

    +
    ", + "type": "text", + }, + ], + "id": "1", + "props": { + "language": "html", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json new file mode 100644 index 0000000000..b9b3e84746 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/codeSpanWithNewline.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "code": true, + }, + "text": "foo bar baz", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json new file mode 100644 index 0000000000..972d1d02df --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/complexDocument.json @@ -0,0 +1,442 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Main Title", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "An introduction paragraph with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " text.", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Section 1", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First bullet point", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Nested point", + "type": "text", + }, + ], + "id": "6", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Second bullet point", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "A notable quote", + "type": "text", + }, + ], + "id": "7", + "props": { + "backgroundColor": "default", + "textColor": "default", + }, + "type": "quote", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Code Example", + "type": "text", + }, + ], + "id": "8", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "function hello() { + return "world"; +}", + "type": "text", + }, + ], + "id": "9", + "props": { + "language": "javascript", + }, + "type": "codeBlock", + }, + { + "children": [], + "content": undefined, + "id": "10", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Section 2", + "type": "text", + }, + ], + "id": "11", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step one", + "type": "text", + }, + ], + "id": "12", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step two", + "type": "text", + }, + ], + "id": "13", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Step three", + "type": "text", + }, + ], + "id": "14", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Feature", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Status", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Bold", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Done", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Italic", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Done", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "15", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": undefined, + "id": "16", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Image", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Final paragraph with ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "a link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": ".", + "type": "text", + }, + ], + "id": "17", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json new file mode 100644 index 0000000000..535c584553 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/deeplyNestedLists.json @@ -0,0 +1,73 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Level 4", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 3", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 2", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Level 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json new file mode 100644 index 0000000000..45f0949abe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/emptyString.json @@ -0,0 +1,13 @@ +[ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json new file mode 100644 index 0000000000..219c8e869a --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/escapedDelimiterInEmphasis.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "*", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json new file mode 100644 index 0000000000..e4ed206bcf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakBackslash.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json new file mode 100644 index 0000000000..359a3b1cbc --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakMultiple.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json new file mode 100644 index 0000000000..e4ed206bcf --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/hardBreakTwoSpaces.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json new file mode 100644 index 0000000000..27be2c1d52 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH1.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json new file mode 100644 index 0000000000..2cb042a21b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH2.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json new file mode 100644 index 0000000000..3e79e93fa0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH3.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 3", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json new file mode 100644 index 0000000000..b5dffeaff8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH4.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 4", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 4, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json new file mode 100644 index 0000000000..9f69aff1a8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH5.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 5", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 5, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json new file mode 100644 index 0000000000..c01c6b3437 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingH6.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 6", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 6, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json new file mode 100644 index 0000000000..c61af59e1d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingInternalPadding.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "foo", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json new file mode 100644 index 0000000000..d32797e0b3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingThenCode.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Code Section", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "x = 42", + "type": "text", + }, + ], + "id": "2", + "props": { + "language": "python", + }, + "type": "codeBlock", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json new file mode 100644 index 0000000000..4a72b6b759 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingTrailingWhitespace.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "foo", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json new file mode 100644 index 0000000000..61518fb6d7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/headingWithInlineStyles.json @@ -0,0 +1,52 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "Italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "Strike", + "type": "text", + }, + { + "styles": {}, + "text": " Heading", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleAsterisks.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleDashes.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json new file mode 100644 index 0000000000..6d2e457fd8 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/horizontalRuleUnderscores.json @@ -0,0 +1,43 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph above", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": {}, + "type": "divider", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph below", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json new file mode 100644 index 0000000000..8ecc4b0a3b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageAngleBracketUrl.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json new file mode 100644 index 0000000000..bd2ec4d1a0 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageNestedBracketsAlt.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt [with] brackets", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json new file mode 100644 index 0000000000..7f7ff95459 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithAlt.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Alt text for image", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/photo.jpg", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json new file mode 100644 index 0000000000..a6279cc8a6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/imageWithTitle.json @@ -0,0 +1,16 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "alt text", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/image.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json new file mode 100644 index 0000000000..7e02a1406d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCode.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "This has ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "inline code", + "type": "text", + }, + { + "styles": {}, + "text": " in it", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json new file mode 100644 index 0000000000..8b77e45fc7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineCodeWithSpecialChars.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Use ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "const x = 42;", + "type": "text", + }, + { + "styles": {}, + "text": " to declare", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json new file mode 100644 index 0000000000..775c811913 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlTag.json @@ -0,0 +1,31 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Hello ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "world", + "type": "text", + }, + { + "styles": {}, + "text": "!", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json new file mode 100644 index 0000000000..a617cae33f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlVoidTag.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one +line two.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json new file mode 100644 index 0000000000..839e11fbb7 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineHtmlWithAttributes.json @@ -0,0 +1,47 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text with ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " and ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": ".", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json new file mode 100644 index 0000000000..35a0bc23d1 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/inlineImage.json @@ -0,0 +1,19 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Text before text after", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json new file mode 100644 index 0000000000..01ec89cd69 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json new file mode 100644 index 0000000000..3e39b28872 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/italicUnderscore.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic with underscores", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json new file mode 100644 index 0000000000..359a3b1cbc --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/lineBreaks.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line one + Line two + Line three", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json new file mode 100644 index 0000000000..4e6bd86d51 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkAndText.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Before ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " after", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json new file mode 100644 index 0000000000..2d0b7cff77 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkBasic.json @@ -0,0 +1,25 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Example", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json new file mode 100644 index 0000000000..0509ca27e9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkInParagraph.json @@ -0,0 +1,35 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check out ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "this link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + { + "styles": {}, + "text": " for more info.", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json new file mode 100644 index 0000000000..17d11b4dde --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithStyledContent.json @@ -0,0 +1,27 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json new file mode 100644 index 0000000000..aac8fd51d6 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/linkWithTitle.json @@ -0,0 +1,25 @@ +[ + { + "children": [], + "content": [ + { + "content": [ + { + "styles": {}, + "text": "example", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json new file mode 100644 index 0000000000..95ff31e2d2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/listWithStyledItems.json @@ -0,0 +1,83 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item with ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "code", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json new file mode 100644 index 0000000000..71ff4f8a21 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedInlineContent.json @@ -0,0 +1,78 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Normal ", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": "bold", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "italic": true, + }, + "text": "italic", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "strike": true, + }, + "text": "strike", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "styles": { + "code": true, + }, + "text": "code", + "type": "text", + }, + { + "styles": {}, + "text": " ", + "type": "text", + }, + { + "content": [ + { + "styles": {}, + "text": "link", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json new file mode 100644 index 0000000000..a877f90459 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/mixedListTypes.json @@ -0,0 +1,90 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Numbered child", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Another numbered", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Bullet item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Check child", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "checked": false, + "textAlignment": "left", + "textColor": "default", + }, + "type": "checkListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Another bullet", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json new file mode 100644 index 0000000000..11f15e765d --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleImages.json @@ -0,0 +1,30 @@ +[ + { + "children": [], + "content": undefined, + "id": "1", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "First", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/first.png", + }, + "type": "image", + }, + { + "children": [], + "content": undefined, + "id": "2", + "props": { + "backgroundColor": "default", + "caption": "", + "name": "Second", + "showPreview": true, + "textAlignment": "left", + "url": "https://example.com/second.png", + }, + "type": "image", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json new file mode 100644 index 0000000000..de4db270fe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/multipleParagraphs.json @@ -0,0 +1,53 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "First paragraph", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second paragraph", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third paragraph", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json new file mode 100644 index 0000000000..d2d96963e9 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedBulletLists.json @@ -0,0 +1,89 @@ +[ + { + "children": [ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Deep nested", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Nested 1", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Nested 2", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "Item 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Item 2", + "type": "text", + }, + ], + "id": "5", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "bulletListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json new file mode 100644 index 0000000000..2e5c63d508 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasis.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "bold and italic", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json new file mode 100644 index 0000000000..717e251a84 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedEmphasisComplex.json @@ -0,0 +1,36 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "bold": true, + }, + "text": "bold ", + "type": "text", + }, + { + "styles": { + "bold": true, + "italic": true, + }, + "text": "bold and italic", + "type": "text", + }, + { + "styles": { + "bold": true, + }, + "text": " bold", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json new file mode 100644 index 0000000000..301593b562 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/nestedOrderedLists.json @@ -0,0 +1,71 @@ +[ + { + "children": [ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Sub first", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Sub second", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + ], + "content": [ + { + "styles": {}, + "text": "First", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Second", + "type": "text", + }, + ], + "id": "4", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json new file mode 100644 index 0000000000..45f0949abe --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/onlyWhitespace.json @@ -0,0 +1,13 @@ +[ + { + "children": [], + "content": [], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json new file mode 100644 index 0000000000..35d2df68a3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/orderedListStart.json @@ -0,0 +1,54 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Third item", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "start": 3, + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Fourth item", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Fifth item", + "type": "text", + }, + ], + "id": "3", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "numberedListItem", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json new file mode 100644 index 0000000000..613cf56ae2 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/paragraphLeadingIndent.json @@ -0,0 +1,20 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "aaa + bbb", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json new file mode 100644 index 0000000000..27be2c1d52 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH1.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 1", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json new file mode 100644 index 0000000000..2cb042a21b --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/setextH2.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Heading 2", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "isToggleable": false, + "level": 2, + "textAlignment": "left", + "textColor": "default", + }, + "type": "heading", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json new file mode 100644 index 0000000000..3cd78e1f12 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/singleNewLines.json @@ -0,0 +1,22 @@ +[ + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Line 1 + Line 2 + Line 3 + Line 4", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json new file mode 100644 index 0000000000..7f504a4b3f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/strikethroughOnly.json @@ -0,0 +1,21 @@ +[ + { + "children": [], + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strikethrough text", + "type": "text", + }, + ], + "id": "1", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json new file mode 100644 index 0000000000..35d1354f9f --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableAlignment.json @@ -0,0 +1,132 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Left", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Center", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Right", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "L", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "C", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "R", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json new file mode 100644 index 0000000000..7a5d236441 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableBasic.json @@ -0,0 +1,135 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Header 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Cell 3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Cell 4", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json new file mode 100644 index 0000000000..36896f03df --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableFollowedByParagraph.json @@ -0,0 +1,114 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Col 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Col 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, + { + "children": [], + "content": [ + { + "styles": {}, + "text": "Paragraph after table", + "type": "text", + }, + ], + "id": "2", + "props": { + "backgroundColor": "default", + "textAlignment": "left", + "textColor": "default", + }, + "type": "paragraph", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json new file mode 100644 index 0000000000..4052304874 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tablePipeless.json @@ -0,0 +1,97 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Col 1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Col 2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json new file mode 100644 index 0000000000..e235519185 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableThreeColumns.json @@ -0,0 +1,132 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "A", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "B", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "C", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "1", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "2", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "3", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json new file mode 100644 index 0000000000..7d0cb2bfe3 --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithInlineFormatting.json @@ -0,0 +1,141 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Header", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Styled", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Normal", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": { + "bold": true, + }, + "text": "Bold", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": { + "italic": true, + }, + "text": "Italic", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": { + "strike": true, + }, + "text": "Strike", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json new file mode 100644 index 0000000000..7ac6eab9fb --- /dev/null +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/tableWithLinks.json @@ -0,0 +1,103 @@ +[ + { + "children": [], + "content": { + "columnWidths": [ + undefined, + undefined, + ], + "headerCols": undefined, + "headerRows": 1, + "rows": [ + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Name", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "styles": {}, + "text": "Link", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + { + "cells": [ + { + "content": [ + { + "styles": {}, + "text": "Example", + "type": "text", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + { + "content": [ + { + "content": [ + { + "styles": {}, + "text": "Click", + "type": "text", + }, + ], + "href": "https://example.com", + "type": "link", + }, + ], + "props": { + "backgroundColor": "default", + "colspan": 1, + "rowspan": 1, + "textAlignment": "left", + "textColor": "default", + }, + "type": "tableCell", + }, + ], + }, + ], + "type": "tableContent", + }, + "id": "1", + "props": { + "textColor": "default", + }, + "type": "table", + }, +] \ No newline at end of file diff --git a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json index 5070e1873e..55635684d9 100644 --- a/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json +++ b/tests/src/unit/core/formatConversion/parse/__snapshots__/markdown/video.json @@ -6,7 +6,7 @@ "props": { "backgroundColor": "default", "caption": "", - "name": "", + "name": "Video", "showPreview": true, "textAlignment": "left", "url": "https://interactive-examples.mdn.mozilla.net/media/cc0-videos/flower.webm", diff --git a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts index d5ad737dd5..5bb72fec41 100644 --- a/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts +++ b/tests/src/unit/core/formatConversion/parse/parseTestInstances.ts @@ -676,7 +676,7 @@ With Hard Break

  • Numbered List Item 2
  • Background Color Paragraph

    -

    !https://www.pulsecarshalton.co.uk/wp-content/uploads/2016/08/jk-placeholder-image.jpg

    +

    !https://placehold.co/800x540.png

    @@ -935,6 +935,96 @@ console.log("Third Line")`, testCase: { name: "divider", content: `
    ` }, executeTest: testParseHTML, }, + { + testCase: { + name: "toggleListItem", + content: `
      +
    • +
      +

      Toggle Item

      +
      +
    • +
    • +
      +

      Toggle Item 2

      +
      +
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleListItemWithChildren", + content: `
      +
    • +
      +

      Toggle Item

      +

      Child 1

      +

      Child 2

      +
      +
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleHeading", + content: `
    +

    Toggle Heading

    +

    Heading Child 1

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleHeadingWithoutChildren", + content: `
    +

    Toggle Heading No Children

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "standaloneDetailsSummary", + content: `
    + Toggle text +

    Child paragraph 1

    +

    Child paragraph 2

    +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "mixedToggleAndBulletList", + content: `
      +
    • Bullet Item
    • +
    • +
      +

      Toggle Item

      +

      Toggle Child

      +
      +
    • +
    • Another Bullet
    • +
    `, + }, + executeTest: testParseHTML, + }, + { + testCase: { + name: "toggleListItemWithImage", + content: `
    + Toggle with image + +

    Text after image

    +
    `, + }, + executeTest: testParseHTML, + }, { testCase: { name: "trailing whitespace", @@ -949,6 +1039,70 @@ console.log("Third Line")`, }, executeTest: testParseHTML, }, + { + testCase: { + name: "msWordPaste", + content: ` + + + + + + + + + + + + +

    Que se passe-t-il si je réponds tard à +un message chat et que l'utilisateur n'est plus en ligne :

    + +

    Lorsque vous envoyez un message à un +utilisateur dans une conversation chat, et qu'il est encore en ligne, il +recevra le message sur sa bulle chatbot.

    + +

    Cependant +S'il n'est plus en ligne, votre message sera envoyé par email si :

    + +

    . +l'utilisateur n'a pas lu votre réponse après 2 minutes

    + +

    . +l'utilisateur n'est plus présent sur votre site web

    + +

     

    + +

    Cela se fait automatiquement donc, lorsque +nous répondons par chat, si l'utilisateur n'est plus là, Crisp renvoie le +message alors par email et le canal de discussion se transforme en canal de +discussion email.
    +
    +Il est possible aussi de créer une conversation email directement le profil de +l'utilisateur (bouton bleu en haut à droite de la conversation)

    + + + + +`, + }, + executeTest: testParseHTML, + }, ]; export const parseTestInstancesMarkdown: TestInstance< @@ -1125,4 +1279,771 @@ Regular paragraph`, }, executeTest: testParseMarkdown, }, + // Individual heading levels + { + testCase: { + name: "headingH1", + content: `# Heading 1`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH2", + content: `## Heading 2`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH3", + content: `### Heading 3`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH4", + content: `#### Heading 4`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH5", + content: `##### Heading 5`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingH6", + content: `###### Heading 6`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "headingWithInlineStyles", + content: `# **Bold** *Italic* ~~Strike~~ Heading`, + }, + executeTest: testParseMarkdown, + }, + // Setext headings + { + testCase: { + name: "setextH1", + content: `Heading 1 +===`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "setextH2", + content: `Heading 2 +---`, + }, + executeTest: testParseMarkdown, + }, + // Code blocks + { + testCase: { + name: "codeBlockBasic", + content: `\`\`\` +console.log('Hello'); +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockWithLanguage", + content: `\`\`\`javascript +const x = 42; +console.log(x); +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockPython", + content: `\`\`\`python +def hello(): + print("Hello, world!") +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockWithSpecialChars", + content: `\`\`\`html +
    +

    Hello **not bold**

    +
    +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "codeBlockTildes", + content: `~~~ +code with tildes +~~~`, + }, + executeTest: testParseMarkdown, + }, + // Horizontal rules + { + testCase: { + name: "horizontalRuleDashes", + content: `Paragraph above + +--- + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "horizontalRuleAsterisks", + content: `Paragraph above + +*** + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "horizontalRuleUnderscores", + content: `Paragraph above + +___ + +Paragraph below`, + }, + executeTest: testParseMarkdown, + }, + // Inline code + { + testCase: { + name: "inlineCode", + content: `This has \`inline code\` in it`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "inlineCodeWithSpecialChars", + content: `Use \`const x = 42;\` to declare`, + }, + executeTest: testParseMarkdown, + }, + // Links + { + testCase: { + name: "linkBasic", + content: `[Example](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkInParagraph", + content: `Check out [this link](https://example.com) for more info.`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkWithStyledContent", + content: `[**Bold link**](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "adjacentLinks", + content: `[Link1](https://example1.com)[Link2](https://example2.com)`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "linkAndText", + content: `Before [Link](https://example.com) after`, + }, + executeTest: testParseMarkdown, + }, + // Tables + { + testCase: { + name: "tableBasic", + content: `| Header 1 | Header 2 | +| -------- | -------- | +| Cell 1 | Cell 2 | +| Cell 3 | Cell 4 |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableThreeColumns", + content: `| A | B | C | +| - | - | - | +| 1 | 2 | 3 |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableWithInlineFormatting", + content: `| Header | Styled | +| ------ | ------ | +| Normal | **Bold** | +| *Italic* | ~~Strike~~ |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableWithLinks", + content: `| Name | Link | +| ---- | ---- | +| Example | [Click](https://example.com) |`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "tableAlignment", + content: `| Left | Center | Right | +| :--- | :----: | ----: | +| L | C | R |`, + }, + executeTest: testParseMarkdown, + }, + // Task lists / check lists + { + testCase: { + name: "checkListBasic", + content: `- [ ] Unchecked item +- [x] Checked item +- [ ] Another unchecked`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "checkListMixed", + content: `- Regular bullet +- [ ] Check item +- [x] Checked item`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "checkListNested", + content: `- [ ] Parent item + - [x] Child checked + - [ ] Child unchecked`, + }, + executeTest: testParseMarkdown, + }, + // Ordered list with start number + { + testCase: { + name: "orderedListStart", + content: `3. Third item +4. Fourth item +5. Fifth item`, + }, + executeTest: testParseMarkdown, + }, + // Hard breaks + { + testCase: { + name: "hardBreakBackslash", + content: `Line one\\ +Line two`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "hardBreakMultiple", + content: `Line one\\ +Line two\\ +Line three`, + }, + executeTest: testParseMarkdown, + }, + // Backslash escapes + { + testCase: { + name: "backslashEscapes", + content: `\\*not bold\\* \\[not a link\\] \\~not strike\\~`, + }, + executeTest: testParseMarkdown, + }, + // Escaped delimiter inside emphasis + { + testCase: { + name: "escapedDelimiterInEmphasis", + content: `*\\**`, + }, + executeTest: testParseMarkdown, + }, + // Nested emphasis + { + testCase: { + name: "nestedEmphasis", + content: `***bold and italic***`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "nestedEmphasisComplex", + content: `**bold *bold and italic* bold**`, + }, + executeTest: testParseMarkdown, + }, + // Individual styles + { + testCase: { + name: "boldOnly", + content: `**Bold text**`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "italicOnly", + content: `*Italic text*`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "strikethroughOnly", + content: `~~Strikethrough text~~`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "boldUnderscore", + content: `__Bold with underscores__`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "italicUnderscore", + content: `_Italic with underscores_`, + }, + executeTest: testParseMarkdown, + }, + // Mixed inline content + { + testCase: { + name: "mixedInlineContent", + content: `Normal **bold** *italic* ~~strike~~ \`code\` [link](https://example.com)`, + }, + executeTest: testParseMarkdown, + }, + // Multiple paragraphs + { + testCase: { + name: "multipleParagraphs", + content: `First paragraph + +Second paragraph + +Third paragraph`, + }, + executeTest: testParseMarkdown, + }, + // Empty content + { + testCase: { + name: "emptyString", + content: ``, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "onlyWhitespace", + content: ` + + `, + }, + executeTest: testParseMarkdown, + }, + // Line breaks + { + testCase: { + name: "lineBreaks", + content: `Line one +Line two +Line three`, + }, + executeTest: testParseMarkdown, + }, + // Nested lists - complex + { + testCase: { + name: "nestedBulletLists", + content: `- Item 1 + - Nested 1 + - Deep nested + - Nested 2 +- Item 2`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "nestedOrderedLists", + content: `1. First + 1. Sub first + 2. Sub second +2. Second`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "mixedListTypes", + content: `- Bullet item + 1. Numbered child + 2. Another numbered +- Another bullet + - [ ] Check child`, + }, + executeTest: testParseMarkdown, + }, + // Blockquote with multiple blocks + { + testCase: { + name: "blockquoteMultiline", + content: `> Line one +> Line two +> Line three`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "blockquoteWithCode", + content: `> Quote with \`inline code\` inside`, + }, + executeTest: testParseMarkdown, + }, + { + testCase: { + name: "blockquoteWithLink", + content: `> Quote with [a link](https://example.com) inside`, + }, + executeTest: testParseMarkdown, + }, + // Blockquote with lazy continuation (no > on continuation lines) + { + testCase: { + name: "blockquoteLazyContinuation", + content: `> This is a quote +that continues here +and here too`, + }, + executeTest: testParseMarkdown, + }, + // Complex document + { + testCase: { + name: "complexDocument", + content: `# Main Title + +An introduction paragraph with **bold** and *italic* text. + +## Section 1 + +- First bullet point +- Second bullet point + - Nested point + +> A notable quote + +### Code Example + +\`\`\`javascript +function hello() { + return "world"; +} +\`\`\` + +--- + +## Section 2 + +1. Step one +2. Step two +3. Step three + +| Feature | Status | +| ------- | ------ | +| Bold | Done | +| Italic | Done | + +![Image](https://example.com/image.png) + +Final paragraph with [a link](https://example.com).`, + }, + executeTest: testParseMarkdown, + }, + // Image with alt text + { + testCase: { + name: "imageWithAlt", + content: `![Alt text for image](https://example.com/photo.jpg)`, + }, + executeTest: testParseMarkdown, + }, + // Multiple images + { + testCase: { + name: "multipleImages", + content: `![First](https://example.com/first.png) + +![Second](https://example.com/second.png)`, + }, + executeTest: testParseMarkdown, + }, + // Inline image within text (should be handled) + { + testCase: { + name: "inlineImage", + content: `Text before ![inline](https://example.com/img.png) text after`, + }, + executeTest: testParseMarkdown, + }, + // Code block immediately after heading + { + testCase: { + name: "headingThenCode", + content: `## Code Section + +\`\`\`python +x = 42 +\`\`\``, + }, + executeTest: testParseMarkdown, + }, + // List with styled items + { + testCase: { + name: "listWithStyledItems", + content: `- **Bold item** +- *Italic item* +- ~~Strikethrough item~~ +- Item with \`code\``, + }, + executeTest: testParseMarkdown, + }, + // Deeply nested lists + { + testCase: { + name: "deeplyNestedLists", + content: `- Level 1 + - Level 2 + - Level 3 + - Level 4`, + }, + executeTest: testParseMarkdown, + }, + // Table followed by paragraph + { + testCase: { + name: "tableFollowedByParagraph", + content: `| Col 1 | Col 2 | +| ----- | ----- | +| A | B | + +Paragraph after table`, + }, + executeTest: testParseMarkdown, + }, + // Paragraphs with various inline formatting + { + testCase: { + name: "adjacentFormattedRuns", + content: `**bold***italic*~~strike~~`, + }, + executeTest: testParseMarkdown, + }, + // Table without outer pipes (GFM allows optional outer pipes) + { + testCase: { + name: "tablePipeless", + content: `Col 1 | Col 2 +----- | ----- +A | B`, + }, + executeTest: testParseMarkdown, + }, + // Indented fenced code block (up to 3 leading spaces per CommonMark) + { + testCase: { + name: "codeBlockIndented", + content: ` \`\`\`ts +const x = 1; + \`\`\``, + }, + executeTest: testParseMarkdown, + }, + // Link with title (title should not appear in href) + { + testCase: { + name: "linkWithTitle", + content: `[example](https://example.com "Example Site")`, + }, + executeTest: testParseMarkdown, + }, + // Image with nested brackets in alt text + { + testCase: { + name: "imageNestedBracketsAlt", + content: `![alt [with] brackets](https://example.com/image.png)`, + }, + executeTest: testParseMarkdown, + }, + // Inline raw HTML tag inside a paragraph passes through verbatim + { + testCase: { + name: "inlineHtmlTag", + content: `Hello world!`, + }, + executeTest: testParseMarkdown, + }, + // Multiple inline HTML tags with attributes + { + testCase: { + name: "inlineHtmlWithAttributes", + content: `Text with bold and link.`, + }, + executeTest: testParseMarkdown, + }, + // A self-closing-style void HTML tag inside a paragraph + { + testCase: { + name: "inlineHtmlVoidTag", + content: `Line one
    line two.`, + }, + executeTest: testParseMarkdown, + }, + // Block-level raw HTML is emitted verbatim — not wrapped in

    + { + testCase: { + name: "blockHtmlDiv", + content: `

    +A warning block. +
    `, + }, + executeTest: testParseMarkdown, + }, + // Block-level HTML comment + { + testCase: { + name: "blockHtmlComment", + content: ` + +Next paragraph.`, + }, + executeTest: testParseMarkdown, + }, + // Bare angle brackets that don't form a valid tag must still be escaped + { + testCase: { + name: "bareAngleBrackets", + content: `1 < 2 and 3 > 0`, + }, + executeTest: testParseMarkdown, + }, + // Block HTML interrupting a paragraph above it + { + testCase: { + name: "blockHtmlInterruptsParagraph", + content: `Some text before. +
    raw block
    +Some text after.`, + }, + executeTest: testParseMarkdown, + }, + // Hard line break via two trailing spaces (CommonMark ex. 633) + { + testCase: { + name: "hardBreakTwoSpaces", + content: `Line one \nLine two`, + }, + executeTest: testParseMarkdown, + }, + // ATX heading: closing #'s and trailing whitespace are stripped (ex. 73) + { + testCase: { + name: "headingTrailingWhitespace", + content: `### foo ### `, + }, + executeTest: testParseMarkdown, + }, + // ATX heading: lots of internal padding still produces a clean heading (ex. 67) + { + testCase: { + name: "headingInternalPadding", + content: `# foo `, + }, + executeTest: testParseMarkdown, + }, + // Code span with internal newline collapses to space (CommonMark ex. 337) + { + testCase: { + name: "codeSpanWithNewline", + content: "`foo bar \nbaz`", + }, + executeTest: testParseMarkdown, + }, + // Image with title attribute (CommonMark ex. 572). The title is parsed + // even if the BlockNote image block doesn't surface it as a prop — + // the point is to not leak `"title"` into the alt or src. + { + testCase: { + name: "imageWithTitle", + content: `![alt text](https://example.com/image.png "An image title")`, + }, + executeTest: testParseMarkdown, + }, + // Angle-bracket-wrapped image URL — brackets are stripped (ex. 580) + { + testCase: { + name: "imageAngleBracketUrl", + content: `![alt]()`, + }, + executeTest: testParseMarkdown, + }, + // Paragraph lines with up to 3 leading spaces of indent are still a + // paragraph; the indent is stripped (CommonMark ex. 222) + { + testCase: { + name: "paragraphLeadingIndent", + content: ` aaa\n bbb`, + }, + executeTest: testParseMarkdown, + }, ]; diff --git a/tests/src/unit/core/schema/__snapshots__/blocks.json b/tests/src/unit/core/schema/__snapshots__/blocks.json index 0bf3c107e4..142a5e7771 100644 --- a/tests/src/unit/core/schema/__snapshots__/blocks.json +++ b/tests/src/unit/core/schema/__snapshots__/blocks.json @@ -270,7 +270,11 @@ }, "node": null, "parse": [Function], + "parseContent": [Function], "render": [Function], + "runsBefore": [ + "toggleListItem", + ], "toExternalHTML": [Function], }, }, @@ -413,6 +417,7 @@ "render": [Function], "runsBefore": [ "default", + "heading", ], "toExternalHTML": [Function], }, @@ -564,7 +569,12 @@ "isolating": false, }, "node": null, + "parse": [Function], + "parseContent": [Function], "render": [Function], + "runsBefore": [ + "bulletListItem", + ], "toExternalHTML": [Function], }, }, diff --git a/tests/src/unit/nextjs/serverUtil.test.ts b/tests/src/unit/nextjs/serverUtil.test.ts new file mode 100644 index 0000000000..39783c04dc --- /dev/null +++ b/tests/src/unit/nextjs/serverUtil.test.ts @@ -0,0 +1,198 @@ +import { execSync, spawn, ChildProcess } from "child_process"; +import { getPort } from "get-port-please"; +import path from "path"; +import { afterAll, beforeAll, describe, expect, it } from "vitest"; + +const TEST_APP_DIR = path.resolve(__dirname, "../../../nextjs-test-app"); +let PORT: number; +let BASE_URL: string; +const MODE = (process.env.NEXTJS_TEST_MODE || "dev") as "dev" | "build"; + +let nextProcess: ChildProcess; +let serverOutput = ""; +let serverErrors = ""; + +/** + * Regression test for #942: @blocknote/server-util must work in Next.js + * App Router server contexts (API routes) with serverExternalPackages. + * + * Set NEXTJS_TEST_MODE=build to test against a production build (slower + * but catches different issues). Defaults to dev mode for fast iteration. + */ +describe(`server-util in Next.js App Router (#942) [${MODE}]`, () => { + beforeAll(async () => { + PORT = await getPort({ portRange: [3900, 4100] }); + BASE_URL = `http://localhost:${PORT}`; + + // Pack and install @blocknote packages as tarballs + execSync("bash setup.sh", { + cwd: TEST_APP_DIR, + stdio: "pipe", + timeout: 240_000, + }); + + if (MODE === "build") { + // Build the Next.js app first + execSync("npx next build", { + cwd: TEST_APP_DIR, + stdio: "pipe", + timeout: 120_000, + }); + + // Start production server + nextProcess = spawn( + "npx", + ["next", "start", "--port", String(PORT)], + { + cwd: TEST_APP_DIR, + stdio: ["ignore", "pipe", "pipe"], + detached: true, + }, + ); + } else { + // Start dev server with Turbopack + nextProcess = spawn( + "npx", + ["next", "dev", "--turbopack", "--port", String(PORT)], + { + cwd: TEST_APP_DIR, + stdio: ["ignore", "pipe", "pipe"], + env: { ...process.env, NODE_ENV: "development" }, + detached: true, + }, + ); + } + + // Wait for "Ready" message + await new Promise((resolve, reject) => { + const timeout = setTimeout(() => { + reject(new Error(`Next.js ${MODE} server did not start within 60s`)); + }, 60_000); + + let stderr = ""; + + nextProcess.stdout?.on("data", (data: Buffer) => { + const text = data.toString(); + serverOutput += text; + if (text.includes("Ready")) { + clearTimeout(timeout); + resolve(); + } + }); + + nextProcess.stderr?.on("data", (data: Buffer) => { + stderr += data.toString(); + serverErrors += data.toString(); + }); + + nextProcess.on("error", (err) => { + clearTimeout(timeout); + reject(err); + }); + + nextProcess.on("exit", (code) => { + if (code !== null && code !== 0) { + clearTimeout(timeout); + reject(new Error(`Next.js exited with code ${code}: ${stderr}`)); + } + }); + }); + }, 180_000); + + afterAll(async () => { + if (nextProcess?.pid == null) { + return; + } + + await new Promise((resolve) => { + nextProcess.once("exit", () => resolve()); + + try { + // Kill the entire process group so Next.js children don't linger + process.kill(-nextProcess.pid!, "SIGTERM"); + } catch { + // Process may have already exited + resolve(); + return; + } + + // Escalate to SIGKILL if still alive after 5s + setTimeout(() => { + try { + process.kill(-nextProcess.pid!, "SIGKILL"); + } catch { + // already gone + } + }, 5_000); + }); + }); + + it("ServerBlockNoteEditor works in API route (mirrors ReactServer.test.tsx)", async () => { + const res = await fetch(`${BASE_URL}/api/server-util`); + const text = await res.text(); + let body: any; + try { + body = JSON.parse(text); + } catch { + const nextDataMatch = text.match( + /