From a6f34d6990608033b7d49fb83149dff88de54575 Mon Sep 17 00:00:00 2001 From: Alex Wild Date: Thu, 3 Apr 2025 21:59:17 +0200 Subject: [PATCH] fix(highlight): Remove zero width space when getting text range --- spec/highlight-support.spec.js | 30 ++++++++++++++++++++++++++++++ spec/selection.spec.js | 11 +++++++++++ src/highlight-support.js | 5 +++-- src/util/dom.js | 5 ++++- 4 files changed, 48 insertions(+), 3 deletions(-) diff --git a/spec/highlight-support.spec.js b/spec/highlight-support.spec.js index bcc410e0..1a6f9c48 100644 --- a/spec/highlight-support.spec.js +++ b/spec/highlight-support.spec.js @@ -327,6 +327,36 @@ ke The
World Go Round`) expect(this.getHtml()).to.equal(expectedHtml) }) + it('limits start and end values to text range', function () { + setupHighlightEnv(this, 'ab') + this.highlightRange('ab', 'myId', -1, 100) + const expectedHtml = this.formatHtml( + `ab` + ) + + expect(this.getHtml()).to.equal(expectedHtml) + }) + + it('always selects the first character if values are too small', function () { + setupHighlightEnv(this, 'ab') + this.highlightRange('ab', 'myId', 0, 0) + const expectedHtml = this.formatHtml( + `ab` + ) + + expect(this.getHtml()).to.equal(expectedHtml) + }) + + it('always selects the last character if values are too large', function () { + setupHighlightEnv(this, 'ab') + this.highlightRange('ab', 'myId', 2, 5) + const expectedHtml = this.formatHtml( + `ab` + ) + + expect(this.getHtml()).to.equal(expectedHtml) + }) + it('handles a
tag without whitespaces', function () { setupHighlightEnv(this, 'a
b') this.highlightRange('b', 'myId', 1, 2) diff --git a/spec/selection.spec.js b/spec/selection.spec.js index ccdc0fb9..4e2bd180 100644 --- a/spec/selection.spec.js +++ b/spec/selection.spec.js @@ -142,6 +142,17 @@ describe('Selection', function () { }) }) + describe('getTextRange()', function () { + + it('handles a zero width non-break space', function () { + const oneWord = createElement('
\uFEFFfoobar\uFEFF
') + const range = createRange() + range.selectNodeContents(oneWord) + const selection = new Selection(oneWord, range) + expect(selection.getTextRange()).to.deep.equal({start: 0, end: 6, text: 'foobar'}) + }) + }) + describe('custom:', function () { beforeEach(function () { diff --git a/src/highlight-support.js b/src/highlight-support.js index 87dd1ce9..08fa28b0 100644 --- a/src/highlight-support.js +++ b/src/highlight-support.js @@ -45,8 +45,9 @@ const highlightSupport = { this.win ) - const actualStartIndex = startIndex - const actualEndIndex = endIndex + // Do not let highlight exceed text range - it should also be at least 1 character long + const actualStartIndex = Math.min(Math.max(startIndex, 0), blockText.length - 1) + const actualEndIndex = Math.min(Math.max(endIndex, 1), blockText.length) highlightText.highlightMatches(editableHost, [{ startIndex: actualStartIndex, diff --git a/src/util/dom.js b/src/util/dom.js index 2dc067ab..a1b91e46 100644 --- a/src/util/dom.js +++ b/src/util/dom.js @@ -182,7 +182,10 @@ export const toCharacterRange = (range, container) => { startRange.setStart(container, 0) startRange.setEnd(range.startContainer, range.startOffset) - const rangeText = range.toString() + // Remove zero width space to make selection more accurate, + // because it will be removed on component blur via extractContent. + const zeroWidthNonBreakingSpace = /\uFEFF/g + const rangeText = range.toString().replace(zeroWidthNonBreakingSpace, '') const start = startRange.toString().length const end = start + rangeText.length