diff --git a/packages/super-editor/src/extensions/search/SearchIndex.js b/packages/super-editor/src/extensions/search/SearchIndex.js index 37970bc671..30d53b8b02 100644 --- a/packages/super-editor/src/extensions/search/SearchIndex.js +++ b/packages/super-editor/src/extensions/search/SearchIndex.js @@ -270,10 +270,22 @@ export class SearchIndex { * @returns {string} Regex pattern string */ static toFlexiblePattern(searchString) { + const hasLeadingWhitespace = /^[\s\u00a0]+/.test(searchString); + const hasTrailingWhitespace = /[\s\u00a0]+$/.test(searchString); + const trimmed = searchString.replace(/^[\s\u00a0]+|[\s\u00a0]+$/g, ''); // 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 parts = trimmed.split(/[\s\u00a0]+/).filter((part) => part.length > 0); + if (parts.length === 0) { + return hasLeadingWhitespace || hasTrailingWhitespace ? '[\\s\\u00a0]+' : ''; + } + let pattern = parts.map((part) => SearchIndex.escapeRegex(part)).join('[\\s\\u00a0]+'); + if (hasLeadingWhitespace) { + pattern = '[\\s\\u00a0]+' + pattern; + } + if (hasTrailingWhitespace) { + pattern = pattern + '[\\s\\u00a0]+'; + } + return pattern; } /** diff --git a/packages/super-editor/src/tests/extensions/track-changes/insertTrackedChange-whitespace.test.js b/packages/super-editor/src/tests/extensions/track-changes/insertTrackedChange-whitespace.test.js new file mode 100644 index 0000000000..111bdca85d --- /dev/null +++ b/packages/super-editor/src/tests/extensions/track-changes/insertTrackedChange-whitespace.test.js @@ -0,0 +1,40 @@ +import { describe, expect, it } from 'vitest'; +import { EditorState } from 'prosemirror-state'; +import { createDocxTestEditor } from '../../helpers/editor-test-utils.js'; + +describe('insertTrackedChange whitespace edge cases (docx)', () => { + it('removes trailing space when search query includes trailing whitespace', () => { + 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('Dallas, Texas ')])]), + paragraph.create(null, []), + paragraph.create(null, [run.create(null, [editor.schema.text('Re: WR Investments')])]), + ]); + + const baseState = EditorState.create({ + schema: editor.schema, + doc: testDoc, + plugins: editor.state.plugins, + }); + editor.setState(baseState); + + const matches = editor.commands.search('Dallas, Texas '); + expect(matches.length).toBeGreaterThan(0); + + const { from, to } = matches[0]; + const inserted = editor.commands.insertTrackedChange({ + from, + to, + text: 'Dallas, Texas', + user: { name: 'Test', email: 'test@example.com' }, + }); + + expect(inserted).toBe(true); + } finally { + editor.destroy(); + } + }); +});