TypeCellOS / TypeCellOS/BlockNote

Off-By-One `RangeError` Crash and Text Duplication in `StyleManager.editLink` and `deleteLink`

Đang mở Phù hợp với người mới
#3,073 0 bình luận 0 reaction 0 người được giao Xem trên GitHub

Chưa có ai nhận issue này.

needs-triage
Ngôn ngữ chính
TypeScript
Star
10.2k
Fork
772
Merge trung bình
3 ngày 11 giờ
Pull request đã merge (30 ngày)
17

Mô tả

What’s broken?

In @blocknote/core, StyleManager.editLink and StyleManager.deleteLink perform an unconditional position + 1 lookup when locating the link mark at the current cursor position:

Inside packages/core/src/editor/managers/StyleManager.ts:220-260:

ts
public editLink(
  url: string,
  text: string,
  position = this.editor.transact((tr) => tr.selection.anchor),
) {
  this.editor.transact((tr) => {
    const linkData = this.getLinkMarkAtPos(position + 1);
    const { from, to } = linkData || {
      from: tr.selection.from,
      to: tr.selection.to,
    };

and in deleteLink:

public deleteLink(
  position = this.editor.transact((tr) => tr.selection.anchor),
) {
  this.editor.transact((tr) => {
    const linkData = this.getLinkMarkAtPos(position + 1);
    const { from, to } = linkData || {
      from: tr.selection.from,
      to: tr.selection.to,
    };
    // 

This causes two major issues:

  1. Unhandled RangeError Crash: If the link is positioned at the very end of the document (position === tr.doc.content.size), position + 1 resolves beyond document bounds. Inside getLinkMarkAtPos, tr.doc.resolve(pos) throws an uncaught RangeError: Position out of range, causing the editor to crash.
  2. Text Duplication & Delete Failure: When the cursor caret is resting at the end of a link (position === link.to), position + 1 queries the character after the link. Since that node lacks the link mark, getLinkMarkAtPos returns undefined and falls back to { from: tr.selection.from, to: tr.selection.to }. Because the selection is a collapsed caret (from === to), editLink inserts the new text beside the old text without replacing it (resulting in OriginalTextEditedText duplication), and deleteLink attempts tr.removeMark(from, to, link) over an empty 0-length range, completely failing to remove the link.
What did you expect to happen?
  1. editLink and deleteLink should never throw an out-of-range error when the cursor is at the end of a document.
  2. When the cursor is positioned at the boundary or end of a link (position === link.to), editLink should successfully replace the existing link text rather than duplicating it, and deleteLink should cleanly remove the link mark.
Steps to reproduce

Scenario A: RangeError Crash

  1. Create a document where a link is the last element: GitHub
  2. Place the cursor at the end of the document (position === editor.prosemirrorState.doc.content.size).
  3. Trigger editor.editLink("https://github.com", "GitHub Homepage") or editor.deleteLink().
  4. Observed: Uncaught RangeError: Position out of range thrown from tr.doc.resolve(pos).

Scenario B: Text Duplication / Failed Deletion

  1. Type a link: Google and place the cursor at the end of the text (position === link.to).
  2. Call editor.editLink("https://google.com", "Google Search").
  3. Observed: The text becomes GoogleGoogle Search instead of Google Search.
  4. Call editor.deleteLink() with the cursor at the same position.
  5. Observed: The link remains active and is not deleted.
BlockNote version

Version: 0.54.0 (and main branch) Package: @blocknote/core

Environment

OS: Any (Windows / macOS / Linux) Browsers: Chrome, Firefox, Safari, Edge Frameworks: Vanilla JS, React, Vue

Additional context

Root Cause

getLinkMarkAtPos expects a valid in-bounds position. Unconditionally adding + 1 breaks on right-boundary cursor positions and document ends.

Proposed Fix

  1. Guard getLinkMarkAtPos against positions exceeding tr.doc.content.size.
  2. Inspect position directly, falling back to position - 1 if the cursor is at the trailing boundary of the link:
--- a/packages/core/src/editor/managers/StyleManager.ts
+++ b/packages/core/src/editor/managers/StyleManager.ts
@@ -154,6 +154,10 @@ export class StyleManager {
     return this.editor.transact((tr) => {
+      const clampedPos = Math.min(Math.max(0, pos), tr.doc.content.size);
+      const resolvedPos = tr.doc.resolve(clampedPos);
       const linkMark = resolvedPos
         .marks()
         .find((mark) => mark.type.name === "link");
@@ -224,7 +228,8 @@ export class StyleManager {
     this.editor.transact((tr) => {
-      const linkData = this.getLinkMarkAtPos(position + 1);
+      const linkData =
+        this.getLinkMarkAtPos(position) ||
+        (position > 0 ? this.getLinkMarkAtPos(position - 1) : undefined);
       const { from, to } = linkData || {
         from: tr.selection.from,
         to: tr.selection.to,
       };
@@ -248,7 +253,8 @@ export class StyleManager {
     this.editor.transact((tr) => {
-      const linkData = this.getLinkMarkAtPos(position + 1);
+      const linkData =
+        this.getLinkMarkAtPos(position) ||
+        (position > 0 ? this.getLinkMarkAtPos(position - 1) : undefined);
       const { from, to } = linkData || {
         from: tr.selection.from,
         to: tr.selection.to,
Contribution
  • I'd be interested in contributing a fix for this issue
Sponsor
  • I'm a sponsor and would appreciate if you could look into this sooner than later 💖

Hướng dẫn đóng góp

Mở hướng dẫn đóng góp

Bắt đầu từ đâu

  1. Đọc hết issue, rồi đọc hướng dẫn đóng góp của dự án.
  2. Bình luận trên issue rằng bạn sẽ nhận — tránh hai người làm cùng một việc.
  3. Fork repository và làm thay đổi trên một nhánh.
  4. Mở pull request có tham chiếu số hiệu của issue.

Hướng nghiên cứu

Bắt đầu trong packages/core/src/editor/managers/StyleManager.ts, tập trung vào getLinkMarkAtPos, editLink và deleteLink quanh các dòng được tham chiếu. Tái hiện cả trường hợp ở cuối tài liệu và trường hợp có link ở cuối, sau đó xác minh rằng các vị trí biên không gây ra exception, việc chỉnh sửa thay thế văn bản của link mà không bị trùng lặp, và việc xóa loại bỏ link mark.

Do mô hình lập chỉ mục viết ra từ nội dung của issue.

Đánh giá

Công nghệ
typescript
Lĩnh vực
frontend
Loại issue
Lỗi
Độ khó
2/5
Thời gian dự kiến
1-3 giờ
Mức độ hoạt động
Sôi nổi
Độ rõ ràng
Đặc tả rõ ràng
Mức phù hợp với người mới
78/100

Nhận issue mới trong hộp thư của bạn

Bản tóm tắt ngắn những issue GitHub phù hợp với người mới.