Skip to content

Commit

Permalink
Merge pull request #3815 from yf-yang/3813
Browse files Browse the repository at this point in the history
fix: #3813
  • Loading branch information
zbeyens authored Nov 27, 2024
2 parents a12a277 + 6c10766 commit 7199ed1
Show file tree
Hide file tree
Showing 4 changed files with 215 additions and 20 deletions.
5 changes: 5 additions & 0 deletions .changeset/fresh-carrots-smile.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@udecode/plate-find-replace': patch
---

fix: FindReplacePlugin supports matching consecutive text nodes
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ it('should be', () => {
expect(
decorateFindReplace({
...getEditorPlugin(editor, FindReplacePlugin),
entry: [{ text: '' }, [0, 0]],
entry: [{ children: [{ text: '' }], type: 'p' }, [0]],
})
).toEqual(output);
});
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,7 @@ it('should decorate matching text', () => {
expect(
plugin.decorate?.({
...getEditorPlugin(editor, plugin),
entry: [{ text: 'test' }, [0, 0]],
entry: [{ children: [{ text: 'test' }], type: 'p' }, [0]],
})
).toEqual([
{
Expand Down Expand Up @@ -45,7 +45,7 @@ it('should decorate matching text case-insensitively', () => {
expect(
plugin.decorate?.({
...getEditorPlugin(editor, plugin),
entry: [{ text: 'test' }, [0, 0]],
entry: [{ children: [{ text: 'test' }], type: 'p' }, [0]],
})
).toEqual([
{
Expand All @@ -62,3 +62,136 @@ it('should decorate matching text case-insensitively', () => {
},
]);
});

it('should decorate matching consecutive text nodes', () => {
const editor = createSlateEditor({
plugins: [FindReplacePlugin],
});

const plugin = editor.getPlugin(FindReplacePlugin);

editor.setOption(FindReplacePlugin, 'search', 'test');

expect(
plugin.decorate?.({
...getEditorPlugin(editor, plugin),
entry: [
{ children: [{ text: 'tes' }, { bold: true, text: 't' }], type: 'p' },
[0],
],
})
).toEqual([
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 0,
path: [0, 0],
},
focus: {
offset: 3,
path: [0, 0],
},
search: 'tes',
},
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 0,
path: [0, 1],
},
focus: {
offset: 1,
path: [0, 1],
},
search: 't',
},
]);
});

it('should decorate matching multiple occurrences', () => {
const editor = createSlateEditor({
plugins: [FindReplacePlugin],
});

const plugin = editor.getPlugin(FindReplacePlugin);

editor.setOption(FindReplacePlugin, 'search', 'test');

expect(
plugin.decorate?.({
...getEditorPlugin(editor, plugin),
entry: [
{
children: [
{ text: 'tes' },
{ bold: true, text: 'ts and tests and t' },
{ text: 'ests' },
],
type: 'p',
},
[0],
],
})
).toEqual([
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 0,
path: [0, 0],
},
focus: {
offset: 3,
path: [0, 0],
},
search: 'tes',
},
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 0,
path: [0, 1],
},
focus: {
offset: 1,
path: [0, 1],
},
search: 't',
},
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 7,
path: [0, 1],
},
focus: {
offset: 11,
path: [0, 1],
},
search: 'test',
},
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 17,
path: [0, 1],
},
focus: {
offset: 18,
path: [0, 1],
},
search: 't',
},
{
[FindReplacePlugin.key]: true,
anchor: {
offset: 0,
path: [0, 2],
},
focus: {
offset: 3,
path: [0, 2],
},
search: 'est',
},
]);
});
91 changes: 74 additions & 17 deletions packages/find-replace/src/lib/decorateFindReplace.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import type { Decorate } from '@udecode/plate-common';
import type { Range } from 'slate';

import { isText } from '@udecode/plate-common';
import { isElement, isText } from '@udecode/plate-common';

import type { FindReplaceConfig } from './FindReplacePlugin';

Expand All @@ -12,27 +12,84 @@ export const decorateFindReplace: Decorate<FindReplaceConfig> = ({
}) => {
const { search } = getOptions();

const ranges: SearchRange[] = [];
if (!(search && isElement(node) && node.children.every(isText))) {
return [];
}

const texts = node.children.map((it) => it.text);
const str = texts.join('').toLowerCase();
const searchLower = search.toLowerCase();

if (!search || !isText(node)) {
return ranges;
let start = 0;
const matches: number[] = [];

while ((start = str.indexOf(searchLower, start)) !== -1) {
matches.push(start);
start += searchLower.length;
}

const { text } = node;
const parts = text.toLowerCase().split(search.toLowerCase());
let offset = 0;
parts.forEach((part, i) => {
if (i !== 0) {
ranges.push({
anchor: { offset: offset - search.length, path },
focus: { offset, path },
search,
[type]: true,
});
if (matches.length === 0) {
return [];
}

const ranges: SearchRange[] = [];
let cumulativePosition = 0;
let matchIndex = 0; // Index in the matches array

for (const [textIndex, text] of texts.entries()) {
const textStart = cumulativePosition;
const textEnd = textStart + text.length;

// Process matches that overlap with the current text node
while (matchIndex < matches.length && matches[matchIndex] < textEnd) {
const matchStart = matches[matchIndex];
const matchEnd = matchStart + search.length;

// If the match ends before the start of the current text, move to the next match
if (matchEnd <= textStart) {
matchIndex++;

continue;
}

// Calculate overlap between the text and the current match
const overlapStart = Math.max(matchStart, textStart);
const overlapEnd = Math.min(matchEnd, textEnd);

if (overlapStart < overlapEnd) {
const anchorOffset = overlapStart - textStart;
const focusOffset = overlapEnd - textStart;

// Corresponding offsets within the search string
const searchOverlapStart = overlapStart - matchStart;
const searchOverlapEnd = overlapEnd - matchStart;

const textNodePath = [...path, textIndex];

ranges.push({
anchor: {
offset: anchorOffset,
path: textNodePath,
},
focus: {
offset: focusOffset,
path: textNodePath,
},
search: search.slice(searchOverlapStart, searchOverlapEnd),
[type]: true,
});
}
// If the match ends within the current text, move to the next match
if (matchEnd <= textEnd) {
matchIndex++;
} else {
// The match continues in the next text node
break;
}
}

offset = offset + part.length + search.length;
});
cumulativePosition = textEnd;
}

return ranges;
};
Expand Down

0 comments on commit 7199ed1

Please sign in to comment.