Skip to content

Commit b5f27a1

Browse files
feat: nextSearchResult & previousSearchResul
1 parent 5ddc4d3 commit b5f27a1

File tree

2 files changed

+71
-26
lines changed

2 files changed

+71
-26
lines changed

demos/vue/src/components/Tiptap.vue

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -78,6 +78,13 @@
7878
>
7979
Clear
8080
</button>
81+
<button
82+
@click="previous"
83+
type="button"
84+
class="relative inline-flex items-center px-4 py-2 -ml-px text-sm font-medium text-gray-700 bg-white border border-gray-300 hover:bg-gray-50 focus:z-10 focus:border-indigo-500 focus:outline-none focus:ring-1 focus:ring-indigo-500"
85+
>
86+
Previous
87+
</button>
8188
<button
8289
@click="next"
8390
type="button"
@@ -101,7 +108,7 @@
101108
</button>
102109

103110
<div class="block text-sm font-medium text-gray-700 py-2 px-4">
104-
Results: {{ editor?.storage?.searchAndReplace?.results.length }}
111+
Results: {{ editor?.storage?.searchAndReplace?.resultIndex + 1 }} / {{ editor?.storage?.searchAndReplace?.results.length }}
105112
</div>
106113
</span>
107114
</div>
@@ -115,8 +122,8 @@ import StarterKit from "@tiptap/starter-kit";
115122
import Link from "@tiptap/extension-link";
116123
import { onMounted, ref, watch } from "vue";
117124
118-
import SearchAndReplace from "@sereneinserenade/tiptap-search-and-replace";
119-
// import { SearchAndReplace } from '../../../../src/searchAndReplace'
125+
// import SearchAndReplace from "@sereneinserenade/tiptap-search-and-replace";
126+
import { SearchAndReplace } from '../../../../src/searchAndReplace'
120127
121128
import GithubIcon from "../assets/github.svg";
122129
@@ -191,7 +198,12 @@ const replace = () => {
191198
};
192199
193200
const next = () => {
194-
editor.value?.commands.next();
201+
editor.value?.commands.nextSearchResult();
202+
goToSelection();
203+
};
204+
205+
const previous = () => {
206+
editor.value?.commands.previousSearchResult();
195207
goToSelection();
196208
};
197209

src/searchAndReplace.ts

Lines changed: 55 additions & 22 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
// MIT License
22

3-
// Copyright (c) 2023 Jeet Mandaliya (Github Username: sereneinserenade)
3+
// Copyright (c) 2023 - 2024 Jeet Mandaliya (Github Username: sereneinserenade)
44

55
// Permission is hereby granted, free of charge, to any person obtaining a copy
66
// of this software and associated documentation files (the "Software"), to deal
@@ -22,7 +22,12 @@
2222

2323
import { Extension, Range, type Dispatch } from "@tiptap/core";
2424
import { Decoration, DecorationSet } from "@tiptap/pm/view";
25-
import { Plugin, PluginKey, type EditorState, type Transaction } from "@tiptap/pm/state";
25+
import {
26+
Plugin,
27+
PluginKey,
28+
type EditorState,
29+
type Transaction,
30+
} from "@tiptap/pm/state";
2631
import { Node as PMNode } from "@tiptap/pm/model";
2732

2833
declare module "@tiptap/core" {
@@ -47,7 +52,11 @@ declare module "@tiptap/core" {
4752
/**
4853
* @description Find next instance of search result.
4954
*/
50-
next: () => ReturnType;
55+
nextSearchResult: () => ReturnType;
56+
/**
57+
* @description Find previous instance of search result.
58+
*/
59+
previousSearchResult: () => ReturnType;
5160
/**
5261
* @description Replace first instance of search result with given replace term.
5362
*/
@@ -88,12 +97,17 @@ function processSearches(
8897
resultIndex: number,
8998
): ProcessedSearches {
9099
const decorations: Decoration[] = [];
91-
let textNodesWithPosition: TextNodesWithPosition[] = [];
92100
const results: Range[] = [];
101+
102+
let textNodesWithPosition: TextNodesWithPosition[] = [];
93103
let index = 0;
94104

95-
if (!searchTerm)
96-
return { decorationsToReturn: DecorationSet.empty, results: [] };
105+
if (!searchTerm) {
106+
return {
107+
decorationsToReturn: DecorationSet.empty,
108+
results: [],
109+
};
110+
}
97111

98112
doc?.descendants((node, pos) => {
99113
if (node.isText) {
@@ -150,21 +164,20 @@ function processSearches(
150164
decorationsToReturn: DecorationSet.create(doc, decorations),
151165
results,
152166
};
153-
};
167+
}
154168

155169
const replace = (
156170
replaceTerm: string,
157171
results: Range[],
158-
index: number,
159172
{ state, dispatch }: { state: EditorState; dispatch: Dispatch },
160173
) => {
161-
const firstResult = results[index]
174+
const firstResult = results[0];
162175

163-
if (!firstResult) return ``
176+
if (!firstResult) return;
164177

165-
const { from, to } = results[index]
178+
const { from, to } = results[0];
166179

167-
dispatch?.(state.tr.insertText(replaceTerm, from, to))
180+
if (dispatch) dispatch(state.tr.insertText(replaceTerm, from, to));
168181
};
169182

170183
const rebaseNextResult = (
@@ -196,13 +209,14 @@ const replaceAll = (
196209
results: Range[],
197210
{ tr, dispatch }: { tr: Transaction; dispatch: Dispatch },
198211
) => {
199-
let offset = 0
200-
let resultsCopy = results.slice()
212+
let offset = 0;
213+
214+
let resultsCopy = results.slice();
201215

202216
if (!resultsCopy.length) return;
203217

204218
for (let i = 0; i < resultsCopy.length; i += 1) {
205-
const { from, to } = resultsCopy[i]
219+
const { from, to } = resultsCopy[i];
206220

207221
tr.insertText(replaceTerm, from, to);
208222

@@ -219,7 +233,7 @@ const replaceAll = (
219233
resultsCopy = rebaseNextResultResponse[1];
220234
}
221235

222-
dispatch?.(tr)
236+
dispatch(tr);
223237
};
224238

225239
export const searchAndReplacePluginKey = new PluginKey(
@@ -298,23 +312,42 @@ export const SearchAndReplace = Extension.create<
298312

299313
return false;
300314
},
301-
next:
315+
nextSearchResult:
302316
() =>
303317
({ editor }) => {
304318
const { results, resultIndex } = editor.storage.searchAndReplace;
305319

306-
if (results[resultIndex + 1]) {
307-
editor.storage.searchAndReplace.resultIndex = resultIndex + 1;
320+
const nextIndex = resultIndex + 1;
321+
322+
if (results[nextIndex]) {
323+
editor.storage.searchAndReplace.resultIndex = nextIndex;
324+
} else {
325+
editor.storage.searchAndReplace.resultIndex = 0;
326+
}
327+
328+
return false;
329+
},
330+
previousSearchResult:
331+
() =>
332+
({ editor }) => {
333+
const { results, resultIndex } = editor.storage.searchAndReplace;
334+
335+
const prevIndex = resultIndex - 1;
336+
337+
if (results[prevIndex]) {
338+
editor.storage.searchAndReplace.resultIndex = prevIndex;
339+
} else {
340+
editor.storage.searchAndReplace.resultIndex = results.length - 1;
308341
}
309342

310343
return false;
311344
},
312345
replace:
313346
() =>
314347
({ editor, state, dispatch }) => {
315-
const { replaceTerm, results, resultIndex } = editor.storage.searchAndReplace;
348+
const { replaceTerm, results } = editor.storage.searchAndReplace;
316349

317-
replace(replaceTerm, results, resultIndex, { state, dispatch });
350+
replace(replaceTerm, results, { state, dispatch });
318351

319352
return false;
320353
},
@@ -377,7 +410,7 @@ export const SearchAndReplace = Extension.create<
377410
doc,
378411
getRegex(searchTerm, disableRegex, caseSensitive),
379412
searchResultClass,
380-
resultIndex
413+
resultIndex,
381414
);
382415

383416
editor.storage.searchAndReplace.results = results;

0 commit comments

Comments
 (0)