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
2222
2323import { Extension , Range , type Dispatch } from "@tiptap/core" ;
2424import { 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" ;
2631import { Node as PMNode } from "@tiptap/pm/model" ;
2732
2833declare 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
155169const 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
170183const 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
225239export 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