Skip to content

Fix drag ghost, image positioning, and tab indentation (#2545 #2570 #2609)#2776

Open
mkcash wants to merge 5 commits into
TypeCellOS:mainfrom
mkcash:fix/drag-ghost-and-position-issues
Open

Fix drag ghost, image positioning, and tab indentation (#2545 #2570 #2609)#2776
mkcash wants to merge 5 commits into
TypeCellOS:mainfrom
mkcash:fix/drag-ghost-and-position-issues

Conversation

@mkcash
Copy link
Copy Markdown

@mkcash mkcash commented May 25, 2026

Summary

Fixes three drag-and-drop and keyboard issues.

#2609 - Drag ghost issues

  • Constrained drag preview max size (300x200px) for oversized ghosts
  • Reset isDragOrigin, menuFrozen, hoveredBlock, view.dragging in blockDragEnd() to prevent blocks becoming undraggable

#2570 - Image drag positioning at end of line

  • When drop target is at right edge of editor, use mouse X position instead of block's right edge for line detection

#2545 - Tab indentation for image blocks

  • Added tabForEmptyBlocks config option for blocks with no text content (image, video)

Files changed

  • packages/core/src/extensions/SideMenu/dragging.ts
  • packages/core/src/extensions/SideMenu/SideMenu.ts
  • packages/core/src/editor/BlockNoteEditor.ts

Summary by CodeRabbit

  • New Features

    • Added a Tab keyboard shortcut to focus the editor when an empty block is selected.
  • Bug Fixes

    • Improved block detection accuracy during drag operations near editor edges.
    • Fixed drag-end cleanup to properly reset all drag-related states and prevent invalid subsequent operations.
    • Limited drag preview size for media blocks to prevent excessively large ghost previews.
  • Documentation

    • Updated deprecation documentation for the keyboard tab behavior option.

Review Change Stack

@vercel
Copy link
Copy Markdown

vercel Bot commented May 25, 2026

@mkcash is attempting to deploy a commit to the TypeCell Team on Vercel.

A member of the Team first needs to authorize it.

@coderabbitai
Copy link
Copy Markdown

coderabbitai Bot commented May 25, 2026

📝 Walkthrough

Walkthrough

Three targeted fixes: updated JSDoc for a deprecated tab option, improved block-hit testing near the editor's right edge, and safer side-menu drag handling (preview size cap and fuller drag cleanup).

Changes

Side Menu Drag Improvements and Documentation

Layer / File(s) Summary
Tab behavior documentation
packages/core/src/editor/BlockNoteEditor.ts
JSDoc for deprecated tabForEmptyBlocks option added/updated, describing Tab/Shift-Tab behavior for blocks without text and pointing to the tabBehavior replacement.
Block positioning near right edge
packages/core/src/extensions/SideMenu/SideMenu.ts
getBlockFromMousePos now checks when the reference block is near the editor's right boundary and uses the mouse X coordinate (instead of the block's right edge) for getBlockFromCoords lookup.
Drag cleanup and preview sizing
packages/core/src/extensions/SideMenu/SideMenu.ts, packages/core/src/extensions/SideMenu/dragging.ts
blockDragEnd expands cleanup to unset the drag image, reset isDragOrigin, clear menuFrozen and hoveredBlock, and null ProseMirror's dragging state. setDragImage caps preview dimensions with maxWidth, maxHeight, and overflow: hidden when the preview exceeds thresholds.

Estimated code review effort

🎯 2 (Simple) | ⏱️ ~12 minutes

Possibly related PRs

Suggested reviewers

  • matthewlipski
  • nperez0111

Poem

🐰 A tiny hop, a careful slide,
The preview shrinks, no longer wide.
Tabs described, the edges tamed,
Drag state cleared, no flags remain.
Hooray — the editor's feeling spry!

🚥 Pre-merge checks | ✅ 4 | ❌ 1

❌ Failed checks (1 warning)

Check name Status Explanation Resolution
Docstring Coverage ⚠️ Warning Docstring coverage is 0.00% 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 directly summarizes the main changes: fixing drag ghost issues, image positioning problems, and tab indentation for image blocks, with clear issue references.
Description check ✅ Passed The description covers Summary, Changes organized by issue, and Files changed; however, Rationale, Impact, Testing, Screenshots, Checklist, and Additional Notes sections are missing.
Linked Issues check ✅ Passed Check skipped because no linked issues were found for this pull request.
Out of Scope Changes check ✅ Passed Check skipped because no linked issues were found for this pull request.

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

✨ Finishing Touches
🧪 Generate unit tests (beta)
  • Create PR with unit tests

Warning

There were issues while running some tools. Please review the errors and either fix the tool's configuration or disable the tool if it's a critical failure.

🔧 ESLint

If the error stems from missing dependencies, add them to the package.json file. For unrecoverable errors (e.g., due to private dependencies), disable the tool in the CodeRabbit configuration.

packages/core/src/extensions/SideMenu/SideMenu.ts

Parsing error: Identifier expected.


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.

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: 4

🤖 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/editor/BlockNoteEditor.ts`:
- Around line 279-280: Update the incomplete JSDoc bullet points in
BlockNoteEditor.ts so each bullet shows the actual option value identifier
before the colon (for example the boolean/enum/string option names used by the
method or property being documented) instead of just "- :"; locate the
surrounding docblock in the BlockNoteEditor class (the comment near the option
descriptions) and replace the two bullets "- : Indents the block." and "- :
(default) Focuses the toolbar if open, otherwise does nothing." with the correct
option tokens used by the code (e.g., the exact literal values or enum
identifiers referenced by the method/parameter) so the mapping between option
value and behavior is explicit.

In `@packages/core/src/extensions/SideMenu/dragging.ts`:
- Around line 124-130: The code uses dragImageElement (typed Element |
undefined) and then reads scrollWidth/scrollHeight and style properties; narrow
and guard it to HTMLElement first. Add an explicit runtime/type check such as if
(dragImageElement && dragImageElement instanceof HTMLElement) { const el =
dragImageElement; const maxWidth = 300; const maxHeight = 200; if
(el.scrollWidth > maxWidth || el.scrollHeight > maxHeight) { el.style.maxWidth =
...; el.style.maxHeight = ...; el.style.overflow = ...; } } so all uses of
scrollWidth/scrollHeight/style operate on a confirmed HTMLElement (referencing
dragImageElement in dragging.ts).

In `@packages/core/src/extensions/SideMenu/SideMenu.ts`:
- Around line 770-773: The code in blockDragEnd accesses the private
SideMenuView.hoveredBlock directly causing a TypeScript access error; add a
public mutator on SideMenuView (e.g., clearHoveredBlock) that sets
this.hoveredBlock = undefined, update blockDragEnd to call
view.clearHoveredBlock() instead of reading/assigning view.hoveredBlock, and
remove direct references to the private hoveredBlock outside the class.
- Around line 119-123: getBlockFromMousePos currently redeclares const
editorBoundingBox (duplicate of the earlier variable) and blockDragEnd illegally
touches a private member view.hoveredBlock; to fix, remove the duplicate
declaration in getBlockFromMousePos and reuse the existing editorBoundingBox
variable already declared earlier in that function (or rename the earlier one if
needed), and stop assigning view.hoveredBlock directly in blockDragEnd—instead
call a public API on SideMenuView (e.g., add/use a method like
clearHoveredBlock() or setHoveredBlock(undefined)) to clear the hovered state;
update blockDragEnd to call that public method (reference symbols:
getBlockFromMousePos, editorBoundingBox, blockDragEnd, SideMenuView,
view.hoveredBlock).
🪄 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: 6ca9ae62-221b-440c-9e29-cf2428f70d4f

📥 Commits

Reviewing files that changed from the base of the PR and between 118d8dc and 8cae0c0.

📒 Files selected for processing (3)
  • packages/core/src/editor/BlockNoteEditor.ts
  • packages/core/src/extensions/SideMenu/SideMenu.ts
  • packages/core/src/extensions/SideMenu/dragging.ts

Comment on lines +279 to +280
* - : Indents the block.
* - : (default) Focuses the toolbar if open, otherwise does nothing.
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

Fix incomplete bullet point descriptions.

The bullet points are missing the option value identifiers before the colons, making it unclear which behavior corresponds to which option value.

📝 Proposed fix to complete the bullet descriptions
-   * - : Indents the block. 
-   * - : (default) Focuses the toolbar if open, otherwise does nothing.
+   * - `"prefer-indent"`: Indents the block.
+   * - `"prefer-navigate-ui"`: (default) Focuses the toolbar if open, otherwise does nothing.
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
* - : Indents the block.
* - : (default) Focuses the toolbar if open, otherwise does nothing.
* - `"prefer-indent"`: Indents the block.
* - `"prefer-navigate-ui"`: (default) Focuses the toolbar if open, otherwise does nothing.
🤖 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/editor/BlockNoteEditor.ts` around lines 279 - 280, Update
the incomplete JSDoc bullet points in BlockNoteEditor.ts so each bullet shows
the actual option value identifier before the colon (for example the
boolean/enum/string option names used by the method or property being
documented) instead of just "- :"; locate the surrounding docblock in the
BlockNoteEditor class (the comment near the option descriptions) and replace the
two bullets "- : Indents the block." and "- : (default) Focuses the toolbar if
open, otherwise does nothing." with the correct option tokens used by the code
(e.g., the exact literal values or enum identifiers referenced by the
method/parameter) so the mapping between option value and behavior is explicit.

Comment on lines +124 to +130
// Constrain drag preview to prevent oversized ghost for media blocks
const maxWidth = 300;
const maxHeight = 200;
if (dragImageElement.scrollWidth > maxWidth || dragImageElement.scrollHeight > maxHeight) {
dragImageElement.style.maxWidth = maxWidth + "px";
dragImageElement.style.maxHeight = maxHeight + "px";
dragImageElement.style.overflow = "hidden";
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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify declaration type and style usage
rg -n -C2 'let dragImageElement: Element|dragImageElement\.style' packages/core/src/extensions/SideMenu/dragging.ts

Repository: TypeCellOS/BlockNote

Length of output: 564


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/core/src/extensions/SideMenu/dragging.ts"

# Show declaration + where it's assigned
rg -n "dragImageElement" "$FILE"
echo "----"
# Show the key region around the assignment(s) and around the problematic access
sed -n '1,220p' "$FILE" | nl -ba | sed -n '1,220p'

Repository: TypeCellOS/BlockNote

Length of output: 1007


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="packages/core/src/extensions/SideMenu/dragging.ts"

echo "=== Lines 1-80 ==="
sed -n '1,80p' "$FILE" | cat -n

echo "=== Lines 80-170 ==="
sed -n '80,170p' "$FILE" | cat -n

Repository: TypeCellOS/BlockNote

Length of output: 8198


Critical: Narrow dragImageElement to HTMLElement before using .style (and scrollWidth/scrollHeight)

dragImageElement is typed as Element | undefined (line ~19), but Lines ~128–130 access dragImageElement.style.*, and the condition also reads dragImageElement.scrollWidth/scrollHeight. This requires an HTMLElement (or an explicit type guard).

🛠️ Proposed fix
-let dragImageElement: Element | undefined;
+let dragImageElement: HTMLElement | undefined;
@@
-  const parentClone = view.domAtPos(from).node.cloneNode(true) as Element;
+  const parentClone = view.domAtPos(from).node.cloneNode(true) as HTMLElement;
🤖 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/extensions/SideMenu/dragging.ts` around lines 124 - 130,
The code uses dragImageElement (typed Element | undefined) and then reads
scrollWidth/scrollHeight and style properties; narrow and guard it to
HTMLElement first. Add an explicit runtime/type check such as if
(dragImageElement && dragImageElement instanceof HTMLElement) { const el =
dragImageElement; const maxWidth = 300; const maxHeight = 200; if
(el.scrollWidth > maxWidth || el.scrollHeight > maxHeight) { el.style.maxWidth =
...; el.style.maxHeight = ...; el.style.overflow = ...; } } so all uses of
scrollWidth/scrollHeight/style operate on a confirmed HTMLElement (referencing
dragImageElement in dragging.ts).

Comment on lines +119 to +123
const editorBoundingBox = (
view.dom.firstChild as HTMLElement
).getBoundingClientRect();
const nearRightEdge =
referenceBlocksBoundingBox.right >= editorBoundingBox.right - 20;
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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify duplicate declaration in function scope
rg -n -C2 'const editorBoundingBox' packages/core/src/extensions/SideMenu/SideMenu.ts

Repository: TypeCellOS/BlockNote

Length of output: 452


🏁 Script executed:

#!/bin/bash
set -euo pipefail

FILE="packages/core/src/extensions/SideMenu/SideMenu.ts"

echo "=== Context around first editorBoundingBox (approx 70-100) ==="
sed -n '60,105p' "$FILE" | cat -n

echo "=== Context around second editorBoundingBox (approx 105-135) ==="
sed -n '105,150p' "$FILE" | cat -n

echo "=== Find hoveredBlock usages ==="
rg -n "hoveredBlock" "$FILE"

Repository: TypeCellOS/BlockNote

Length of output: 3814


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="packages/core/src/extensions/SideMenu/SideMenu.ts"

sed -n '730,800p' "$FILE" | cat -n

Repository: TypeCellOS/BlockNote

Length of output: 2534


🏁 Script executed:

#!/bin/bash
set -euo pipefail
FILE="packages/core/src/extensions/SideMenu/SideMenu.ts"

echo "=== Declaration(s) of outer `view` variable(s) ==="
rg -n --hidden "(^|\s)(let|var|const)\s+view\b" "$FILE"

echo "=== Context for `private hoveredBlock` ==="
rg -n "private hoveredBlock" "$FILE"
START=130
END=180
sed -n "${START},${END}p" "$FILE" | cat -n

Repository: TypeCellOS/BlockNote

Length of output: 1984


Fix TypeScript compile errors in SideMenu.ts (duplicate const + private access)

  • getBlockFromMousePos redeclares const editorBoundingBox at ~line 119 even though it’s already declared in the same function at ~line 83 (duplicate block-scoped declaration).
  • blockDragEnd assigns view.hoveredBlock = undefined, but hoveredBlock is private on SideMenuView (illegal private-member access outside the class).
🛠️ Proposed fix (remove duplicate `editorBoundingBox`)
-  const editorBoundingBox = (
-    view.dom.firstChild as HTMLElement
-  ).getBoundingClientRect();
   const nearRightEdge = 
     referenceBlocksBoundingBox.right >= editorBoundingBox.right - 20;
🤖 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/extensions/SideMenu/SideMenu.ts` around lines 119 - 123,
getBlockFromMousePos currently redeclares const editorBoundingBox (duplicate of
the earlier variable) and blockDragEnd illegally touches a private member
view.hoveredBlock; to fix, remove the duplicate declaration in
getBlockFromMousePos and reuse the existing editorBoundingBox variable already
declared earlier in that function (or rename the earlier one if needed), and
stop assigning view.hoveredBlock directly in blockDragEnd—instead call a public
API on SideMenuView (e.g., add/use a method like clearHoveredBlock() or
setHoveredBlock(undefined)) to clear the hovered state; update blockDragEnd to
call that public method (reference symbols: getBlockFromMousePos,
editorBoundingBox, blockDragEnd, SideMenuView, view.hoveredBlock).

Comment on lines +770 to +773
// Clear any stale drag references
if (view.hoveredBlock) {
view.hoveredBlock = undefined;
}
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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
# Verify private declaration and external access
rg -n -C2 'private hoveredBlock|view\.hoveredBlock' packages/core/src/extensions/SideMenu/SideMenu.ts

Repository: TypeCellOS/BlockNote

Length of output: 417


Fix TypeScript private access to hoveredBlock in blockDragEnd

SideMenu.ts declares hoveredBlock as private (line 151), but blockDragEnd clears it by reading/assigning view.hoveredBlock (lines 771–773), which will be a TypeScript access error.

🛠️ Proposed fix
-        if (view.hoveredBlock) {
-          view.hoveredBlock = undefined;
-        }
+        view.clearHoveredBlock();
// Add inside SideMenuView class
public clearHoveredBlock = () => {
  this.hoveredBlock = undefined;
};
🤖 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/extensions/SideMenu/SideMenu.ts` around lines 770 - 773,
The code in blockDragEnd accesses the private SideMenuView.hoveredBlock directly
causing a TypeScript access error; add a public mutator on SideMenuView (e.g.,
clearHoveredBlock) that sets this.hoveredBlock = undefined, update blockDragEnd
to call view.clearHoveredBlock() instead of reading/assigning view.hoveredBlock,
and remove direct references to the private hoveredBlock outside the class.

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

🤖 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/extensions/SideMenu/SideMenu.ts`:
- Around line 817-831: The Tab keyboard handler contains invalid selection
access and wrong block API usage and the drag-end code touches a private field:
replace the parse-error sel..node()/sel..before() with proper ProseMirror
selection access (use selection.node if it's a NodeSelection or
selection.$anchor.node()/selection.$anchor.before() / selection.$from if you
need start; reference the selection from editor.prosemirrorView.state.selection
in the Tab handler), then actually invoke the block indentation API using a
valid BlockIdentifier (do not pass a raw numeric PM position to editor.getBlock;
instead resolve a block id or pass { id: <blockId> } to editor.getBlock and call
the editor method that performs indentation/indentBlock after focusing), and
finally stop assigning to the private hoveredBlock on SideMenuView in
blockDragEnd—use a public setter or method on SideMenuView (e.g.,
setHoveredBlock/clearHoveredBlock) to clear it instead of directly mutating the
private field.
🪄 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: 3401eca5-5910-42ed-9b27-a14f254032d2

📥 Commits

Reviewing files that changed from the base of the PR and between 8cae0c0 and 4c46d1c.

📒 Files selected for processing (1)
  • packages/core/src/extensions/SideMenu/SideMenu.ts

Comment on lines +817 to +831
keyboardShortcuts: {
Tab: ({ editor }) => {
const sel = editor.prosemirrorView.state.selection;
const node = sel..node();
// If the selected block has no content (image, video, file, divider),
// trigger indentation via the block API
if (node && node.type.spec.content === "none" && editor.isEditable) {
const blockInfo = editor.getBlock(sel..before());
if (blockInfo) {
editor.focus();
return true;
}
}
return false;
},
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 | 🔴 Critical | ⚡ Quick win

🧩 Analysis chain

🌐 Web query:

ProseMirror NodeSelection $from node() method

💡 Result:

In ProseMirror, NodeSelection doesn’t have a “$from node” method. The relevant constructors are: 1) new NodeSelection($pos: ResolvedPos) - This is the “$from” style: you pass a ResolvedPos (typically called $from in code). It creates a node selection pointing at the node after that resolved position (it uses $pos.nodeAfter and the nodeSize to compute the selection range) [1][2]. - It “does not verify the validity of its argument,” so you must ensure the resolved position actually points to a node you can select [1][2]. 2) NodeSelection.create(doc: Node, from: number) - This creates a node selection from a non-resolved position (a numeric position in a given document) [1][2]. How from/to/anchor/head are set - A node selection targets a single node; in such a selection, from and to point directly before and after the selected node, anchor equals from, and head equals to [1][2]. Common pitfall (why you can end up selecting the “wrong” node) - Creating a node selection “from a resolved position” can select an unexpected node depending on which resolved position boundary you used; community discussion notes this confusion and that you usually need to use the correct position for the target node [3]. Example (select a node using a ResolvedPos) - If you have a ResolvedPos named $from that resolves to the position before the node you want, you can do: const sel = new NodeSelection($from) // selects the nodeAfter of $from [1][2] If you meant a specific API like NodeSelection.$from or $from.node, please paste the exact snippet you saw—then I can map it to the correct ProseMirror API surface.

Citations:


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Locate the SideMenu file and inspect around the Tab handler region
sed -n '760,880p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Find how getBlock is implemented/used and what arguments it expects
rg -n "getBlock\\(" packages/core/src | head
rg -n "nestBlock\\(" packages/core/src | head
rg -n "indent" packages/core/src/extensions/SideMenu/SideMenu.ts packages/core/src | head -n 50

# Inspect editor/block API definitions (files that likely declare getBlock or nestBlock)
# Using fd to find likely candidates by filename
fd -a "getBlock" packages/core/src || true
fd -a "nestBlock" packages/core/src || true

# Broader: find the symbol definitions by searching for "getBlock" and "nestBlock" declarations
rg -n "function getBlock|getBlock\\s*:\\s*\\(|getBlock\\s*\\(" packages/core/src | head -n 50
rg -n "nestBlock\\s*:\\s*\\(|function nestBlock|nestBlock\\s*\\(" packages/core/src | head -n 50

# Search for hoveredBlock access in SideMenu
rg -n "hoveredBlock" packages/core/src/extensions/SideMenu/SideMenu.ts

Repository: TypeCellOS/BlockNote

Length of output: 20040


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the Tab handler with correct line context
sed -n '800,850p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Inspect SideMenu view class field for hoveredBlock and where blockDragEnd is defined
sed -n '120,320p' packages/core/src/extensions/SideMenu/SideMenu.ts
sed -n '740,790p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Inspect editor/block APIs: getBlock signature and nestBlock implementation
sed -n '880,970p' packages/core/src/editor/BlockNoteEditor.ts
sed -n '180,270p' packages/core/src/editor/managers/BlockManager.ts
sed -n '1220,1285p' packages/core/src/editor/BlockNoteEditor.ts

sed -n '1,260p' packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts

# Inspect getBlock-from-pos helpers (argument types)
sed -n '1,220p' packages/core/src/api/blockManipulation/getBlock/getBlock.ts
sed -n '1,170p' packages/core/src/api/getBlockInfoFromPos.ts
sed -n '1,170p' packages/core/src/schema/blocks/internal.ts

Repository: TypeCellOS/BlockNote

Length of output: 36982


🏁 Script executed:

#!/bin/bash
set -euo pipefail

rg -n "export type BlockIdentifier" packages/core/src | head -n 50
rg -n "type BlockIdentifier" packages/core/src/schema packages/core/src | head -n 50

# Show getBlockManager.nestBlock implementation and/or nestBlock command
sed -n '1,120p' packages/core/src/editor/managers/BlockManager.ts
sed -n '180,260p' packages/core/src/editor/managers/BlockManager.ts
sed -n '1,260p' packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts

# Inspect canNestBlock or related usage maybe helps selection-based nesting
sed -n '260,360p' packages/core/src/api/blockManipulation/commands/nestBlock/nestBlock.ts

# Inspect getBlockFromPos helper used in SideMenu or elsewhere
sed -n '1,120p' packages/core/src/extensions/SideMenu/SideMenu.ts
sed -n '240,320p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Inspect getBlockInfoFromSelection helpers which likely use ResolvedPos / selection
sed -n '200,320p' packages/core/src/api/getBlockInfoFromPos.ts

rg -n "getBlockInfoFromSelection\\(" packages/core/src | head -n 50

# Confirm getBlockInfoFromSelection signature and how to get beforePos etc.

Repository: TypeCellOS/BlockNote

Length of output: 25763


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect Tab handling in KeyboardShortcutsExtension.ts
sed -n '880,1040p' packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts

# Search for correct ProseMirror NodeSelection usage patterns in this repo
rg -n "NodeSelection" packages/core/src | head -n 50
rg -n "selection\\.node\\b|\\bsel\\.node\\b|\\.node\\(\\)" packages/core/src/extensions packages/core/src/editor packages/core/src/api packages/core/src/blocks | head -n 50
rg -n "\\$from\\b|\\$anchor\\b|\\.before\\(" packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts packages/core/src/api packages/core/src/editor packages/core/src/extensions | head -n 100

# Look for existing tab indentation logic that likely nests the selected block
rg -n "tabForEmptyBlocks|prefer-indent|nestBlock\\(" packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts
rg -n "nestBlock\\(this\\.options\\.editor\\)|editor\\.nestBlock\\(" packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts packages/core/src/extensions/SideMenu/SideMenu.ts packages/core/src | head -n 50

# Inspect selection-to-block helper usage patterns
rg -n "getBlockInfoFromSelection\\(state\\)|getBlockInfoFromSelection\\(view\\.state\\)" packages/core/src/extensions/tiptap-extensions/KeyboardShortcuts/KeyboardShortcutsExtension.ts | head -n 50

Repository: TypeCellOS/BlockNote

Length of output: 11170


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show SideMenu.ts Tab handler with a bit more context (to see intended selection type / surrounding logic)
sed -n '800,840p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Inspect how SideMenu dragging treats selection types (NodeSelection / MultipleNodeSelection)
sed -n '1,120p' packages/core/src/extensions/SideMenu/dragging.ts
sed -n '160,230p' packages/core/src/extensions/SideMenu/dragging.ts

# Inspect MultipleNodeSelection implementation to understand what $anchor/$head/$from/$to expose
sed -n '1,220p' packages/core/src/extensions/SideMenu/MultipleNodeSelection.ts

# Inspect any usage of selection.$anchor/$head in repo for node/position access
rg -n "selection\\.node\\b|\\.selection\\.node\\b|\\bsel\\.node\\b|\\$anchor\\.node\\(|\\$head\\.node\\(" packages/core/src | head -n 50

# Inspect getBlockInfoFromSelection usage for indentation logic
rg -n "getBlockInfoFromSelection\\(.*\\).*nestBlock|nestBlock\\(" packages/core/src/extensions packages/core/src/api packages/core/src/editor | head -n 50

Repository: TypeCellOS/BlockNote

Length of output: 11541


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Show the exact locations + context for blockDragEnd (where view.hoveredBlock is touched)
nl -ba packages/core/src/extensions/SideMenu/SideMenu.ts | sed -n '680,840p'

# Show where SideMenuView class starts and hoveredBlock is declared
nl -ba packages/core/src/extensions/SideMenu/SideMenu.ts | sed -n '140,340p'

# Show BlockNoteEditor.getBlock / nestBlock signatures to ensure correct calls
nl -ba packages/core/src/editor/BlockNoteEditor.ts | sed -n '880,980p'
nl -ba packages/core/src/editor/BlockNoteEditor.ts | sed -n '1220,1280p'

Repository: TypeCellOS/BlockNote

Length of output: 107


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Print with line numbers via cat -n (nl isn't available in sandbox)
sed -n '140,340p' packages/core/src/extensions/SideMenu/SideMenu.ts | cat -n

sed -n '680,840p' packages/core/src/extensions/SideMenu/SideMenu.ts | cat -n

sed -n '880,980p' packages/core/src/editor/BlockNoteEditor.ts | cat -n
sed -n '1220,1280p' packages/core/src/editor/BlockNoteEditor.ts | cat -n

Repository: TypeCellOS/BlockNote

Length of output: 19478


🏁 Script executed:

#!/bin/bash
set -euo pipefail

# Inspect SideMenu.ts imports (to see whether NodeSelection/ResolvedPos are imported)
sed -n '1,120p' packages/core/src/extensions/SideMenu/SideMenu.ts

# Look for selection.$anchor usage patterns in SideMenu.ts
rg -n "selection\\.|\\.\\$anchor|\\$anchor\\.node\\(|\\.\\$from|\\$from\\.before\\(|\\.before\\(" packages/core/src/extensions/SideMenu/SideMenu.ts

# Look for other incorrect patterns sel..before / sel..node in repo
rg -n "sel\\.\\.|\\$anchor\\.\\.|\\.\\.node\\(|\\.\\.before\\(" packages/core/src/extensions/SideMenu/SideMenu.ts packages/core/src | head -n 50

# Inspect nearby helper methods for indentation via block API (if any) in SideMenu.ts
rg -n "nestBlock\\(|unnestBlock\\(|nestBlock" packages/core/src/extensions/SideMenu/SideMenu.ts

# Inspect getBlock usage around line 820 for expected argument type
sed -n '780,840p' packages/core/src/extensions/SideMenu/SideMenu.ts

Repository: TypeCellOS/BlockNote

Length of output: 3702


Fix invalid selection access and missing nesting in SideMenu Tab shortcut

  • Lines 820 & 824: sel..node() / sel..before() contain invalid .. syntax (won’t parse). Use the ProseMirror selection APIs instead (e.g., selection.node for NodeSelection, or selection.$anchor.node() / selection.$anchor.before()).
  • The handler never actually indents: it only calls editor.focus() after editor.getBlock(...). Also, editor.getBlock expects a BlockIdentifier (string or { id }), not a numeric PM position.
  • blockDragEnd assigns view.hoveredBlock = undefined, but hoveredBlock is declared private on SideMenuView; TypeScript will reject this—use a public method/setter instead.
🧰 Tools
🪛 Biome (2.4.15)

[error] 820-820: Expected an identifier but instead found '.'.

(parse)


[error] 824-824: Expected an identifier but instead found '.'.

(parse)

🤖 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/extensions/SideMenu/SideMenu.ts` around lines 817 - 831,
The Tab keyboard handler contains invalid selection access and wrong block API
usage and the drag-end code touches a private field: replace the parse-error
sel..node()/sel..before() with proper ProseMirror selection access (use
selection.node if it's a NodeSelection or
selection.$anchor.node()/selection.$anchor.before() / selection.$from if you
need start; reference the selection from editor.prosemirrorView.state.selection
in the Tab handler), then actually invoke the block indentation API using a
valid BlockIdentifier (do not pass a raw numeric PM position to editor.getBlock;
instead resolve a block id or pass { id: <blockId> } to editor.getBlock and call
the editor method that performs indentation/indentBlock after focusing), and
finally stop assigning to the private hoveredBlock on SideMenuView in
blockDragEnd—use a public setter or method on SideMenuView (e.g.,
setHoveredBlock/clearHoveredBlock) to clear it instead of directly mutating the
private field.

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.

1 participant