Skip to content

feat: Argument to select moved block in moveBlocksUp/Down (BLO-907)#2723

Open
matthewlipski wants to merge 4 commits intomainfrom
move-blocks-arg
Open

feat: Argument to select moved block in moveBlocksUp/Down (BLO-907)#2723
matthewlipski wants to merge 4 commits intomainfrom
move-blocks-arg

Conversation

@matthewlipski
Copy link
Copy Markdown
Collaborator

@matthewlipski matthewlipski commented May 7, 2026

Summary

This PR adds a blockIdentifier optional argument to moveBlocksUp and moveBlocksDown. If specified, the provided block is moved up/down. Nothing happens if the block could not be found. In not provided, the selected blocks are moved, i.e. the same behaviour as before is used.

Closes #1414

Rationale

This makes the functions more useful for consumers.

Changes

  • Added moveBlocks helper function.
  • Added optional blockIdentifier: BlockIdentifier argument to moveBlocksUp and moveBlocksDown.
  • Updated docs.

Impact

N/A

Testing

Added unit tests.

Screenshots/Video

N/A

Checklist

  • Code follows the project's coding standards.
  • Unit tests covering the new feature have been added.
  • All existing tests pass.
  • The documentation has been updated to reflect the new feature

Additional Notes

N/A

Summary by CodeRabbit

  • New Features

    • moveBlocksUp() and moveBlocksDown() methods now accept an optional block identifier parameter, enabling precise block movement while preserving current selection.
  • Documentation

    • Updated API documentation with examples for the enhanced block movement functionality.
  • Tests

    • Extended test coverage for moving specific blocks and validating selection stability.

@vercel
Copy link
Copy Markdown

vercel Bot commented May 7, 2026

The latest updates on your projects. Learn more about Vercel for GitHub.

Project Deployment Actions Updated (UTC)
blocknote Ready Ready Preview May 7, 2026 6:19pm
blocknote-website Ready Ready Preview May 7, 2026 6:19pm

Request Review

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 7, 2026

Review Change Stack

📝 Walkthrough

Walkthrough

This PR adds optional blockIdentifier parameters to moveBlocksUp() and moveBlocksDown(), enabling targeted block movement without affecting selection. A new reusable moveBlocks() function centralizes block remove/insert logic. Signature updates propagate through BlockManager and BlockNoteEditor public APIs with comprehensive test coverage and documentation.

Changes

Block Movement with Optional Identifier

Layer / File(s) Summary
Core moveBlocks Function
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
New exported moveBlocks(editor, blocks, referenceBlock, placement) function removes blocks, flattens columnList descendants, and reinserts them before/after reference block.
Selection-based Move Refactoring
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
moveSelectedBlocksAndSelection now delegates remove/insert to moveBlocks while preserving selection save/restore behavior.
Direction Commands
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
moveBlocksUp and moveBlocksDown accept optional blockIdentifier; routes to moveBlocks (explicit block) or moveSelectedBlocksAndSelection (selection-driven).
Public API Updates
packages/core/src/editor/managers/BlockManager.ts, packages/core/src/editor/BlockNoteEditor.ts
BlockManager and BlockNoteEditor method signatures updated to expose optional blockIdentifier parameter with aligned doc comments.
Tests & Documentation
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts, docs/content/docs/reference/editor/manipulating-content.mdx
Test imports extended for selection stability assertions; new test cases validate explicit block moves, selection preservation, boundary no-ops, and nested blocks; API documentation updated with new signatures and dual-behavior examples.

Estimated code review effort

🎯 3 (Moderate) | ⏱️ ~25 minutes

Suggested reviewers

  • nperez0111

Poem

🐰 Hop up, hop down, with blocks in tow,
A tiny param makes movement flow,
Selection stays put while targets take flight,
Optional moves make menus just right!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 66.67% which is insufficient. The required threshold is 80.00%. Write docstrings for the functions missing them to satisfy the coverage threshold.
✅ Passed checks (4 passed)
Check name Status Explanation
Title check ✅ Passed The title clearly and specifically describes the main feature: adding an optional argument to moveBlocksUp/Down functions, including the issue reference.
Description check ✅ Passed The description follows the template structure with all major sections completed: Summary, Rationale, Changes, Impact, Testing, and Checklist all filled out appropriately.
Linked Issues check ✅ Passed The PR successfully implements the requested feature from issue #1414: exports moveBlocksUp/Down with optional blockIdentifier parameter to move specific blocks, addressing the original request.
Out of Scope Changes check ✅ Passed All changes are within scope: optional argument added to moveBlocksUp/Down, moveBlocks helper exported, documentation updated, and tests added. Documentation changes are appropriate for API updates.

✏️ Tip: You can configure your own custom pre-merge checks in the settings.

✨ Finishing Touches
📝 Generate docstrings
  • Create stacked PR
  • Commit on current branch
🧪 Generate unit tests (beta)
  • Create PR with unit tests
  • Commit unit tests in branch move-blocks-arg

Thanks for using CodeRabbit! It's free for OSS, and your support helps us grow. If you like it, consider giving us a shout-out.

❤️ Share

Comment @coderabbitai help to get the list of available commands and usage tips.

@pkg-pr-new
Copy link
Copy Markdown

pkg-pr-new Bot commented May 7, 2026

Open in StackBlitz

@blocknote/ariakit

npm i https://pkg.pr.new/@blocknote/ariakit@2723

@blocknote/code-block

npm i https://pkg.pr.new/@blocknote/code-block@2723

@blocknote/core

npm i https://pkg.pr.new/@blocknote/core@2723

@blocknote/mantine

npm i https://pkg.pr.new/@blocknote/mantine@2723

@blocknote/react

npm i https://pkg.pr.new/@blocknote/react@2723

@blocknote/server-util

npm i https://pkg.pr.new/@blocknote/server-util@2723

@blocknote/shadcn

npm i https://pkg.pr.new/@blocknote/shadcn@2723

@blocknote/xl-ai

npm i https://pkg.pr.new/@blocknote/xl-ai@2723

@blocknote/xl-docx-exporter

npm i https://pkg.pr.new/@blocknote/xl-docx-exporter@2723

@blocknote/xl-email-exporter

npm i https://pkg.pr.new/@blocknote/xl-email-exporter@2723

@blocknote/xl-multi-column

npm i https://pkg.pr.new/@blocknote/xl-multi-column@2723

@blocknote/xl-odt-exporter

npm i https://pkg.pr.new/@blocknote/xl-odt-exporter@2723

@blocknote/xl-pdf-exporter

npm i https://pkg.pr.new/@blocknote/xl-pdf-exporter@2723

commit: 0caea13

Copy link
Copy Markdown

@coderabbitai coderabbitai Bot left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Actionable comments posted: 1

🧹 Nitpick comments (1)
packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts (1)

312-354: 💤 Low value

Duplicated if (blockIdentifier) branch in moveBlocksUp / moveBlocksDown

Both functions check if (blockIdentifier) twice: once to resolve sourceBlock (lines 318 / 362) and again to decide between moveBlocks vs. moveSelectedBlocksAndSelection (lines 339 / 384). Since sourceBlock is always defined by the time the second check runs (early return on !sourceBlock), a boolean local variable or a single refactored branch would make the intent clearer and eliminate the duplication.

♻️ Suggested simplification (excerpt for `moveBlocksUp`)
 export function moveBlocksUp(
   editor: BlockNoteEditor<any, any, any>,
   blockIdentifier?: BlockIdentifier,
 ) {
   editor.transact(() => {
-    let sourceBlock: Block<any, any, any> | undefined;
-    if (blockIdentifier) {
-      sourceBlock = editor.getBlock(blockIdentifier);
-      if (!sourceBlock) {
-        return;
-      }
-    } else {
-      const selection = editor.getSelection();
-      sourceBlock =
-        selection?.blocks[0] || editor.getTextCursorPosition().block;
-    }
+    const isExplicit = blockIdentifier !== undefined;
+    let sourceBlock: Block<any, any, any>;
+    if (isExplicit) {
+      const found = editor.getBlock(blockIdentifier!);
+      if (!found) return;
+      sourceBlock = found;
+    } else {
+      const selection = editor.getSelection();
+      sourceBlock =
+        selection?.blocks[0] || editor.getTextCursorPosition().block;
+    }

     const moveUpPlacement = getMoveUpPlacement(
       editor,
       editor.getPrevBlock(sourceBlock),
       editor.getParentBlock(sourceBlock),
     );

     if (!moveUpPlacement) {
       return;
     }

-    if (blockIdentifier) {
+    if (isExplicit) {
       moveBlocks(editor, [sourceBlock], moveUpPlacement.referenceBlock, moveUpPlacement.placement);
     } else {
       moveSelectedBlocksAndSelection(editor, moveUpPlacement.referenceBlock, moveUpPlacement.placement);
     }
   });
 }

Also applies to: 356-399

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts`
around lines 312 - 354, The duplicated if (blockIdentifier) branches in
moveBlocksUp (and likewise in moveBlocksDown) should be consolidated: after
resolving sourceBlock (using editor.getBlock(blockIdentifier) or selection
logic) capture a boolean like isExplicit = Boolean(blockIdentifier) (or rename
to hadIdentifier) right after you ensure sourceBlock exists, then use that
boolean for the later branch to call moveBlocks(...) vs
moveSelectedBlocksAndSelection(...); this removes the second repeated
blockIdentifier check while preserving the early-return behavior when
sourceBlock is undefined and keeps intent clear (reference symbols:
moveBlocksUp, moveBlocksDown, sourceBlock, editor.getBlock, moveBlocks,
moveSelectedBlocksAndSelection).
🤖 Prompt for all review comments with AI agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

Inline comments:
In `@packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts`:
- Around line 160-170: The moveBlocks function currently always calls
flattenColumns, which destroys a moved columnList node when callers (e.g.,
moveBlocksUp/moveBlocksDown) supply an explicit single block identifier; fix by
only flattening when appropriate: detect if blocks.length === 1 and the sole
block is a columnList (check block.type or Block.kind used in your model) and in
that case call editor.insertBlocks with the original block (preserving the
columnList) instead of flattenColumns; alternatively add an optional parameter
(e.g., preserveStructure: boolean) to moveBlocks and thread it through callers
(moveBlocksUp/moveBlocksDown) so callers can opt-in to preserving columnList,
and add a unit test asserting that moving a single columnList preserves its
structure.

---

Nitpick comments:
In `@packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts`:
- Around line 312-354: The duplicated if (blockIdentifier) branches in
moveBlocksUp (and likewise in moveBlocksDown) should be consolidated: after
resolving sourceBlock (using editor.getBlock(blockIdentifier) or selection
logic) capture a boolean like isExplicit = Boolean(blockIdentifier) (or rename
to hadIdentifier) right after you ensure sourceBlock exists, then use that
boolean for the later branch to call moveBlocks(...) vs
moveSelectedBlocksAndSelection(...); this removes the second repeated
blockIdentifier check while preserving the early-return behavior when
sourceBlock is undefined and keeps intent clear (reference symbols:
moveBlocksUp, moveBlocksDown, sourceBlock, editor.getBlock, moveBlocks,
moveSelectedBlocksAndSelection).
🪄 Autofix (Beta)

Fix all unresolved CodeRabbit comments on this PR:

  • Push a commit to this branch (recommended)
  • Create a new PR with the fixes

ℹ️ Review info
⚙️ Run configuration

Configuration used: Organization UI

Review profile: CHILL

Plan: Pro

Run ID: 5e58c310-3426-4e0a-b5e0-0777f550cf98

📥 Commits

Reviewing files that changed from the base of the PR and between 1b53232 and 0caea13.

⛔ Files ignored due to path filters (1)
  • packages/core/src/api/blockManipulation/commands/moveBlocks/__snapshots__/moveBlocks.test.ts.snap is excluded by !**/*.snap, !**/__snapshots__/**
📒 Files selected for processing (5)
  • docs/content/docs/reference/editor/manipulating-content.mdx
  • packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.test.ts
  • packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts
  • packages/core/src/editor/BlockNoteEditor.ts
  • packages/core/src/editor/managers/BlockManager.ts

Comment on lines +160 to +170
export function moveBlocks(
editor: BlockNoteEditor<any, any, any>,
blocks: Block<any, any, any>[],
referenceBlock: BlockIdentifier,
placement: "before" | "after",
) {
editor.transact(() => {
editor.removeBlocks(blocks);
editor.insertBlocks(flattenColumns(blocks), referenceBlock, placement);
});
}
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

moveBlocks always applies flattenColumns, which destructures columnList blocks moved by explicit identifier

When moveBlocksUp/moveBlocksDown is called with a blockIdentifier that points to a columnList block, moveBlocks will:

  1. Remove the entire columnList node.
  2. Insert only the inner content (blockContainer nodes extracted from each column) via flattenColumns.

The columnList structure is permanently destroyed rather than moved. This is the same behavior as selection-based movement (the pre-existing logic), but it is more surprising in the explicit-identifier path since callers directly name the block they intend to move. There is no test or documentation covering this case.

If moving a columnList as a unit is a valid use case, consider skipping flattenColumns when blocks contains only a single explicit block (or expose a flag). At a minimum, adding a test that asserts this behavior makes the implicit contract explicit.

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@packages/core/src/api/blockManipulation/commands/moveBlocks/moveBlocks.ts`
around lines 160 - 170, The moveBlocks function currently always calls
flattenColumns, which destroys a moved columnList node when callers (e.g.,
moveBlocksUp/moveBlocksDown) supply an explicit single block identifier; fix by
only flattening when appropriate: detect if blocks.length === 1 and the sole
block is a columnList (check block.type or Block.kind used in your model) and in
that case call editor.insertBlocks with the original block (preserving the
columnList) instead of flattenColumns; alternatively add an optional parameter
(e.g., preserveStructure: boolean) to moveBlocks and thread it through callers
(moveBlocksUp/moveBlocksDown) so callers can opt-in to preserving columnList,
and add a unit test asserting that moving a single columnList preserves its
structure.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Export editor.moveBlocksUp() with a optional parameter blockId, or export another function replace.

1 participant