From 2117a0f879a7c7a81544c919d6d83c818be9a4e8 Mon Sep 17 00:00:00 2001 From: Johannes Marbach Date: Thu, 22 Jun 2023 20:01:36 +0200 Subject: [PATCH] Fix caret jump when backspacing into empty line at beginning of editor Fixes: vector-im/element-web#22335 --- src/editor/caret.ts | 16 ++++++++++++---- test/editor/caret-test.ts | 16 ++++++++++++++++ 2 files changed, 28 insertions(+), 4 deletions(-) diff --git a/src/editor/caret.ts b/src/editor/caret.ts index 61c6a856400..ebd13828afc 100644 --- a/src/editor/caret.ts +++ b/src/editor/caret.ts @@ -104,10 +104,10 @@ export function getLineAndNodePosition( } { const { parts } = model; const partIndex = caretPosition.index; - const lineResult = findNodeInLineForPart(parts, partIndex); + let { offset } = caretPosition; + const lineResult = findNodeInLineForPart(parts, partIndex, offset); const { lineIndex } = lineResult; let { nodeIndex } = lineResult; - let { offset } = caretPosition; // we're at an empty line between a newline part // and another newline part or end/start of parts. // set offset to 0 so it gets set to the
inside the line container @@ -120,7 +120,11 @@ export function getLineAndNodePosition( return { lineIndex, nodeIndex, offset }; } -function findNodeInLineForPart(parts: Part[], partIndex: number): { lineIndex: number; nodeIndex: number } { +function findNodeInLineForPart( + parts: Part[], + partIndex: number, + offset: number, +): { lineIndex: number; nodeIndex: number } { let lineIndex = 0; let nodeIndex = -1; @@ -130,6 +134,10 @@ function findNodeInLineForPart(parts: Part[], partIndex: number): { lineIndex: n for (let i = 0; i <= partIndex; ++i) { const part = parts[i]; if (part.type === Type.Newline) { + // don't jump over the linebreak if the offset is before it + if (i == partIndex && offset === 0) { + continue; + } lineIndex += 1; nodeIndex = -1; prevPart = undefined; @@ -140,7 +148,7 @@ function findNodeInLineForPart(parts: Part[], partIndex: number): { lineIndex: n } // only jump over caret node if we're not at our destination node already, // as we'll assume in moveOutOfUnselectablePart that nodeIndex - // refers to the node corresponding to the part, + // refers to the node corresponding to the part, // and not an adjacent caret node if (i < partIndex) { const nextPart = parts[i + 1]; diff --git a/test/editor/caret-test.ts b/test/editor/caret-test.ts index caa8b092956..d51e2bdae6d 100644 --- a/test/editor/caret-test.ts +++ b/test/editor/caret-test.ts @@ -46,6 +46,14 @@ describe("editor/caret: DOM position for caret", function () { }); }); describe("handling line breaks", function () { + it("at start of first line which is empty", function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.newline(), pc.plain("hello world")], pc); + const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 0 }); + expect(lineIndex).toBe(0); + expect(nodeIndex).toBe(-1); + expect(offset).toBe(0); + }); it("at end of last line", function () { const pc = createPartCreator(); const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.plain("world")], pc); @@ -62,6 +70,14 @@ describe("editor/caret: DOM position for caret", function () { expect(nodeIndex).toBe(0); expect(offset).toBe(0); }); + it("before empty line", function () { + const pc = createPartCreator(); + const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.newline(), pc.plain("world")], pc); + const { offset, lineIndex, nodeIndex } = getLineAndNodePosition(model, { index: 0, offset: 5 }); + expect(lineIndex).toBe(0); + expect(nodeIndex).toBe(0); + expect(offset).toBe(5); + }); it("in empty line", function () { const pc = createPartCreator(); const model = new EditorModel([pc.plain("hello"), pc.newline(), pc.newline(), pc.plain("world")], pc);