Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 80 additions & 0 deletions packages/core/src/editor/managers/StyleManager.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,80 @@
import { describe, expect, it } from "vite-plus/test";

import { BlockNoteEditor } from "../BlockNoteEditor.js";

/**
* @vitest-environment jsdom
*/

function createEditorWithLink() {
const editor = BlockNoteEditor.create();
editor.mount(document.createElement("div"));
editor.replaceBlocks(editor.document, [
{
id: "block",
type: "paragraph",
content: [
{
type: "link",
href: "https://google.com",
content: [{ type: "text", text: "Google", styles: {} }],
},
],
},
]);
return editor;
}

function linkContent(editor: BlockNoteEditor<any, any, any>) {
const block = editor.getBlock("block")!;
return (block.content as any[]).map((inline) => ({
type: inline.type,
href: inline.href,
text: inline.type === "link" ? inline.content[0].text : inline.text,
}));
}

describe("StyleManager link editing at boundaries", () => {
it("edits the link when the caret is at its end", () => {
const editor = createEditorWithLink();
editor.setTextCursorPosition("block", "end");

editor.editLink("https://google.com", "Google Search");

expect(linkContent(editor)).toEqual([
{ type: "link", href: "https://google.com", text: "Google Search" },
]);
});

it("deletes the link when the caret is at its end", () => {
const editor = createEditorWithLink();
editor.setTextCursorPosition("block", "end");

editor.deleteLink();

expect(linkContent(editor)).toEqual([
{ type: "text", href: undefined, text: "Google" },
]);
});

it("edits the link when the caret is at its start", () => {
const editor = createEditorWithLink();
editor.setTextCursorPosition("block", "start");

editor.editLink("https://google.com", "Google Search");

expect(linkContent(editor)).toEqual([
{ type: "link", href: "https://google.com", text: "Google Search" },
]);
});

it("does not throw for a position at the very end of the document", () => {
const editor = createEditorWithLink();
const end = editor.prosemirrorState.doc.content.size;

expect(() =>
editor.editLink("https://github.com", "GitHub", end),
).not.toThrow();
expect(() => editor.deleteLink(end)).not.toThrow();
});
});
23 changes: 21 additions & 2 deletions packages/core/src/editor/managers/StyleManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -175,6 +175,25 @@ export class StyleManager<
});
}

/**
* Find the link mark touching `position`: inside it, or at its start or end boundary,
* where the resolved position's marks alone would miss a non-inclusive link.
* Positions outside the document are skipped, so a caret at the very end never throws.
*/
private getLinkMarkAround(position: number) {
const size = this.editor.transact((tr) => tr.doc.content.size);
for (const pos of [position + 1, position, position - 1]) {
if (pos < 0 || pos > size) {
continue;
}
const linkData = this.getLinkMarkAtPos(pos);
if (linkData && position >= linkData.from && position <= linkData.to) {
return linkData;
}
}
return undefined;
}

/**
* Gets the URL of the last link in the current selection, or `undefined` if there are no links in the selection.
*/
Expand Down Expand Up @@ -222,7 +241,7 @@ export class StyleManager<
position = this.editor.transact((tr) => tr.selection.anchor),
) {
this.editor.transact((tr) => {
const linkData = this.getLinkMarkAtPos(position + 1);
const linkData = this.getLinkMarkAround(position);
const { from, to } = linkData || {
from: tr.selection.from,
to: tr.selection.to,
Expand All @@ -246,7 +265,7 @@ export class StyleManager<
position = this.editor.transact((tr) => tr.selection.anchor),
) {
this.editor.transact((tr) => {
const linkData = this.getLinkMarkAtPos(position + 1);
const linkData = this.getLinkMarkAround(position);
const { from, to } = linkData || {
from: tr.selection.from,
to: tr.selection.to,
Expand Down