Off-By-One `RangeError` Crash and Text Duplication in `StyleManager.editLink` and `deleteLink`
Dieses Issue hat noch niemand übernommen.
Bewertung
- Schwierigkeit
- 2/5
- Geschätzter Aufwand
- 1-3 Stunden
- Anfängerfreundlichkeit
- 78/100
- Issue-Typ
- Bug
- Klarheit
- Klar beschrieben
- Aktivitätsstatus
- Aktiv
- Tech-Stack
- typescript
- Bereich
- frontend
Rechercherichtung
Beginnen Sie in packages/core/src/editor/managers/StyleManager.ts und konzentrieren Sie sich auf getLinkMarkAtPos, editLink und deleteLink in der Nähe der referenzierten Zeilen. Reproduzieren Sie sowohl den Fall am Ende des Dokuments als auch den Fall mit einem abschließenden Link. Überprüfen Sie anschließend, dass Grenzpositionen keine Exception auslösen, das Bearbeiten den Linktext ohne Duplizierung ersetzt und das Löschen das Link-Mark entfernt.
Vom Indexierungsmodell aus dem Issue-Text verfasst.
Beschreibung
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:
- 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.
- 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?
- editLink and deleteLink should never throw an out-of-range error when the cursor is at the end of a document.
- 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
- Create a document where a link is the last element: GitHub
- Place the cursor at the end of the document (position === editor.prosemirrorState.doc.content.size).
- Trigger editor.editLink("https://github.com", "GitHub Homepage") or editor.deleteLink().
- Observed: Uncaught RangeError: Position out of range thrown from tr.doc.resolve(pos).
Scenario B: Text Duplication / Failed Deletion
- Type a link: Google and place the cursor at the end of the text (position === link.to).
- Call editor.editLink("https://google.com", "Google Search").
- Observed: The text becomes GoogleGoogle Search instead of Google Search.
- Call editor.deleteLink() with the cursor at the same position.
- 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
- Guard getLinkMarkAtPos against positions exceeding tr.doc.content.size.
- 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 💖
- Vorherrschende Sprache
- TypeScript
- Sterne
- 10.2k
- Forks
- 772
- Ø Merge
- 3 T. 11 Std.
- Gemergte PRs (30 T.)
- 17
Beitragsleitfaden
Erste Schritte
- Lesen Sie das ganze Issue und danach den Beitragsleitfaden des Projekts.
- Schreiben Sie ins Issue, dass Sie es übernehmen — das erspart doppelte Arbeit.
- Forken Sie das Repository und arbeiten Sie in einem Branch.
- Öffnen Sie einen Pull Request, der die Issue-Nummer nennt.
Mehr aus TypeCellOS/BlockNote
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
TypeCellOS/BlockNote#3098 ·
-
needs-triage
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 85/100
TypeCellOS/BlockNote#3072 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 82/100
TypeCellOS/BlockNote#2949 · 1 Kommentar ·
-
a11y
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
TypeCellOS/BlockNote#2855 ·
-
a11y
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 62/100
TypeCellOS/BlockNote#2829 · 1 Kommentar ·
Alle Issues in TypeCellOS/BlockNote
Ähnliche Issues
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 84/100
Eynzof/Hermes-CN-Desktop#610 ·
-
bug clawsweeper:linked-pr-open clawsweeper:needs-live-repro clawsweeper:no-new-fix-pr impact:message-loss issue-rating: 🐚 platinum hermit P2 regression
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 78/100
-
enhancement
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 68/100
-
calcite-components needs triage refactor
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 75/100
Esri/calcite-design-system#15203 ·
-
Schwierigkeit 2/5 1-3 Stunden Anfängerfreundlichkeit 90/100
danielmiessler/LifeOS#2218 ·