https://sonarly.com/issue/33776?type=bug Pressing Backspace at the beginning of the first block in a Dashboard rich text widget throws an unhandled RangeError from ProseMirror, crashing the editor. Fix: Added a tiptap Extension (`backspaceTopLevelGuard`) that intercepts Backspace keypresses at the beginning of the first top-level block in BlockNote editors, preventing BlockNote 0.47.x's internal parent-block traversal from calling `resolvedPos.before(0)` which throws `RangeError: "There is no position before the top-level node"`. The extension: 1. Only activates on Backspace with an empty (collapsed) selection 2. Checks if the cursor is at offset 0 within a node at depth ≤ 2 (BlockNote's doc → blockGroup → blockContainer structure) 3. Confirms the position is at the document start (`$from.before($from.depth) === 1`) 4. Returns `true` (handled) to swallow the keypress — there is nothing to delete at this position anyway Applied to both `useCreateBlockNote` call sites: - `StandaloneRichTextEditorContent.tsx` (Dashboard rich text widget — the crash site from the Sentry event) - `RichTextFieldEditor.tsx` (Record field rich text editor — same BlockNote version, same potential crash) Uses `_tiptapOptions.extensions` to inject the guard extension into BlockNote's underlying tiptap editor.
39 lines
1.2 KiB
TypeScript
39 lines
1.2 KiB
TypeScript
import { Extension } from '@tiptap/core';
|
|
|
|
// Guards against a BlockNote 0.47.x bug where pressing Backspace at the
|
|
// beginning of the first top-level block throws:
|
|
// RangeError: "There is no position before the top-level node"
|
|
// BlockNote's internal parent-block traversal calls
|
|
// resolvedPos.before(depth - 1) without checking that depth > 1.
|
|
export const getBackspaceTopLevelGuardExtension = () =>
|
|
Extension.create({
|
|
name: 'backspaceTopLevelGuard',
|
|
priority: 1000,
|
|
addKeyboardShortcuts() {
|
|
return {
|
|
Backspace: ({ editor }) => {
|
|
const { $from, empty } = editor.state.selection;
|
|
|
|
if (!empty) {
|
|
return false;
|
|
}
|
|
|
|
// When the cursor is at the very beginning of the first
|
|
// top-level block (offset 0 and depth ≤ 2 in BlockNote's
|
|
// doc → blockGroup → blockContainer structure), swallow the
|
|
// Backspace to prevent the crash. There is nothing to delete
|
|
// in this position anyway.
|
|
if ($from.parentOffset === 0 && $from.depth <= 2) {
|
|
const atDocStart = $from.before($from.depth) === 1;
|
|
|
|
if (atDocStart) {
|
|
return true;
|
|
}
|
|
}
|
|
|
|
return false;
|
|
},
|
|
};
|
|
},
|
|
});
|