From 6fe9cb7b0f0de7a1c603e8b7233ab89f61f9cf30 Mon Sep 17 00:00:00 2001 From: cam Date: Mon, 19 Jan 2026 14:45:22 -0800 Subject: [PATCH] fix: match search across block breaks without whitespace --- .../src/extensions/search/SearchIndex.js | 8 +++- .../search/search-cross-paragraph.test.js | 47 +++++++++++++++++++ 2 files changed, 54 insertions(+), 1 deletion(-) diff --git a/packages/super-editor/src/extensions/search/SearchIndex.js b/packages/super-editor/src/extensions/search/SearchIndex.js index 37970bc67..ed15702a8 100644 --- a/packages/super-editor/src/extensions/search/SearchIndex.js +++ b/packages/super-editor/src/extensions/search/SearchIndex.js @@ -273,7 +273,13 @@ export class SearchIndex { // Split by whitespace (including non-breaking spaces), escape each part, rejoin with flexible whitespace pattern const parts = searchString.split(/[\s\u00a0]+/).filter((part) => part.length > 0); if (parts.length === 0) return ''; - return parts.map((part) => SearchIndex.escapeRegex(part)).join('[\\s\\u00a0]+'); + const blockSeparatorPattern = '(?:\\n)?'; + const escapedParts = parts.map((part) => { + const chars = Array.from(part); + if (chars.length === 0) return ''; + return chars.map((ch) => SearchIndex.escapeRegex(ch)).join(blockSeparatorPattern); + }); + return escapedParts.join('[\\s\\u00a0]+'); } /** diff --git a/packages/super-editor/src/tests/extensions/search/search-cross-paragraph.test.js b/packages/super-editor/src/tests/extensions/search/search-cross-paragraph.test.js index b5afef3ab..2c6838244 100644 --- a/packages/super-editor/src/tests/extensions/search/search-cross-paragraph.test.js +++ b/packages/super-editor/src/tests/extensions/search/search-cross-paragraph.test.js @@ -122,6 +122,27 @@ describe('Cross-paragraph search', () => { } }); + it('should find matches across paragraph boundaries without whitespace in query', () => { + const editor = createDocxTestEditor(); + + try { + const { doc, paragraph, run } = editor.schema.nodes; + const testDoc = doc.create(null, [ + paragraph.create(null, [run.create(null, [editor.schema.text('February 7, 2023')])]), + paragraph.create(null, [run.create(null, [editor.schema.text('Via Electronic Mail')])]), + ]); + + const index = new SearchIndex(); + index.build(testDoc); + + const matches = index.search('2023Via'); + + expect(matches.length).toBeGreaterThan(0); + } finally { + editor.destroy(); + } + }); + it('should handle case-insensitive search', () => { const editor = createDocxTestEditor(); @@ -252,6 +273,32 @@ describe('Cross-paragraph search', () => { editor.destroy(); } }); + + it('should map cross-paragraph match without whitespace to multiple ranges', () => { + const editor = createDocxTestEditor(); + + try { + const { doc, paragraph, run } = editor.schema.nodes; + const testDoc = doc.create(null, [ + paragraph.create(null, [run.create(null, [editor.schema.text('February 7, 2023')])]), + paragraph.create(null, [run.create(null, [editor.schema.text('Via Electronic Mail')])]), + ]); + + const index = new SearchIndex(); + index.build(testDoc); + + const matches = index.search('2023Via'); + expect(matches.length).toBeGreaterThan(0); + + const ranges = index.offsetRangeToDocRanges(matches[0].start, matches[0].end); + expect(ranges).toHaveLength(2); + + const combinedText = ranges.map((range) => testDoc.textBetween(range.from, range.to)).join(''); + expect(combinedText).toBe('2023Via'); + } finally { + editor.destroy(); + } + }); }); });