@@ -20,11 +20,12 @@ import { Shortcut } from './shortcut';
20
20
* @returns {JSX.Element }
21
21
*/
22
22
const SearchReplaceForBlockEditor = ( ) : JSX . Element => {
23
- const [ replacements , setReplacements ] = useState ( 0 ) ;
24
- const [ isModalVisible , setIsModalVisible ] = useState ( false ) ;
25
- const [ searchInput , setSearchInput ] = useState ( '' ) ;
26
- const [ replaceInput , setReplaceInput ] = useState ( '' ) ;
27
- const [ caseSensitive , setCaseSensitive ] = useState ( false ) ;
23
+ const [ replacements , setReplacements ] = useState ( 0 ) ;
24
+ const [ isModalVisible , setIsModalVisible ] = useState ( false ) ;
25
+ const [ searchInput , setSearchInput ] = useState ( '' ) ;
26
+ const [ replaceInput , setReplaceInput ] = useState ( '' ) ;
27
+ const [ caseSensitive , setCaseSensitive ] = useState ( false ) ;
28
+ const [ context , setContext ] = useState ( false ) ;
28
29
29
30
/**
30
31
* Open Modal.
@@ -34,8 +35,8 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
34
35
* @returns {void }
35
36
*/
36
37
const openModal = ( ) : void => {
37
- setIsModalVisible ( true ) ;
38
- setReplacements ( 0 ) ;
38
+ setIsModalVisible ( true ) ;
39
+ setReplacements ( 0 ) ;
39
40
}
40
41
41
42
/**
@@ -46,8 +47,8 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
46
47
* @returns {void }
47
48
*/
48
49
const closeModal = ( ) : void => {
49
- setIsModalVisible ( false ) ;
50
- setReplacements ( 0 ) ;
50
+ setIsModalVisible ( false ) ;
51
+ setReplacements ( 0 ) ;
51
52
}
52
53
53
54
/**
@@ -64,11 +65,25 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
64
65
const selectedText : string = getBlockEditorIframe ( ) . getSelection ( ) . toString ( ) ;
65
66
const modalSelector : string = '.search-replace-modal' ;
66
67
67
- if ( selectedText && ! inContainer ( modalSelector ) ) {
68
- setSearchInput ( selectedText ) ;
68
+ if ( selectedText && ! inContainer ( modalSelector ) ) {
69
+ setSearchInput ( selectedText ) ;
69
70
}
70
71
} ;
71
72
73
+ /**
74
+ * Listen for changes in Search Input & Case Sensitivity.
75
+ *
76
+ * By passing in a FALSY context to the replace callback, we only
77
+ * search for matched strings, we DO NOT replace matched strings.
78
+ *
79
+ * @since 1.3.0
80
+ *
81
+ * @returns {void }
82
+ */
83
+ useEffect ( ( ) => {
84
+ replace ( ) ;
85
+ } , [ searchInput , caseSensitive ] ) ;
86
+
72
87
/**
73
88
* Handle case sensitive toggle feature
74
89
* to enable user perform case-sensitive search
@@ -79,7 +94,7 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
79
94
* @param {boolean } newValue
80
95
* @returns {void }
81
96
*/
82
- const handleCaseSensitive = ( newValue : boolean ) : void => {
97
+ const handleCaseSensitive = ( newValue : boolean ) : void => {
83
98
setCaseSensitive ( newValue ) ;
84
99
}
85
100
@@ -88,13 +103,16 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
88
103
* clicks the 'Replace' button.
89
104
*
90
105
* @since 1.0.0
106
+ * @since 1.3.0 Pass in context param to determine if it is Search or Replace.
91
107
*
108
+ * @param {boolean } context True (Replace), False (Search).
92
109
* @returns {void }
93
110
*/
94
- const replace = ( ) : void => {
95
- setReplacements ( 0 ) ;
111
+ const replace = ( context : boolean = false ) : void => {
112
+ setContext ( context ) ;
113
+ setReplacements ( 0 ) ;
96
114
97
- if ( ! searchInput ) {
115
+ if ( ! searchInput ) {
98
116
return ;
99
117
}
100
118
@@ -103,9 +121,9 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
103
121
isCaseSensitive ( ) || caseSensitive ? 'g' : 'gi'
104
122
) ;
105
123
106
- select ( 'core/block-editor' ) . getBlocks ( ) . forEach ( ( element ) => {
107
- recursivelyReplace ( element , pattern , replaceInput ) ;
108
- } ) ;
124
+ select ( 'core/block-editor' ) . getBlocks ( ) . forEach ( ( element ) => {
125
+ recursivelyReplace ( element , pattern , replaceInput , context ) ;
126
+ } ) ;
109
127
} ;
110
128
111
129
/**
@@ -115,37 +133,39 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
115
133
*
116
134
* @since 1.0.0
117
135
* @since 1.0.1 Handle edge-cases for quote, pullquote & details block.
136
+ * @since 1.3.0 Pass in context param to determine if it is Search or Replace.
118
137
*
119
138
* @param {Object } element Gutenberg editor block.
120
139
* @param {RegExp } pattern Search pattern.
121
140
* @param {string } text Replace pattern.
141
+ * @param {boolean } context True (Replace), False (Search).
122
142
*
123
143
* @returns {void }
124
144
*/
125
- const recursivelyReplace = ( element , pattern , text ) : void => {
126
- if ( getAllowedBlocks ( ) . indexOf ( element . name ) !== - 1 ) {
127
- const args = { element, pattern, text } ;
145
+ const recursivelyReplace = ( element , pattern , text , context ) : void => {
146
+ if ( getAllowedBlocks ( ) . indexOf ( element . name ) !== - 1 ) {
147
+ const args = { element, pattern, text, context } ;
128
148
129
- switch ( element . name ) {
149
+ switch ( element . name ) {
130
150
case 'core/quote' :
131
151
case 'core/pullquote' :
132
- replaceBlockAttribute ( args , 'citation' ) ;
152
+ replaceBlockAttribute ( args , 'citation' ) ;
133
153
break ;
134
154
135
155
case 'core/details' :
136
- replaceBlockAttribute ( args , 'summary' ) ;
156
+ replaceBlockAttribute ( args , 'summary' ) ;
137
157
break ;
138
158
139
159
default :
140
- replaceBlockAttribute ( args , 'content' ) ;
160
+ replaceBlockAttribute ( args , 'content' ) ;
141
161
break ;
142
162
}
143
163
}
144
164
145
- if ( element . innerBlocks . length ) {
146
- element . innerBlocks . forEach ( ( innerElement ) => {
147
- recursivelyReplace ( innerElement , pattern , text ) ;
148
- } ) ;
165
+ if ( element . innerBlocks . length ) {
166
+ element . innerBlocks . forEach ( ( innerElement ) => {
167
+ recursivelyReplace ( innerElement , pattern , text , context ) ;
168
+ } ) ;
149
169
}
150
170
}
151
171
@@ -161,33 +181,38 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
161
181
*
162
182
* @returns {void }
163
183
*/
164
- const replaceBlockAttribute = ( args , attribute ) : void => {
184
+ const replaceBlockAttribute = ( args , attribute ) : void => {
165
185
const { attributes, clientId } = args . element ;
166
186
167
- if ( undefined === attributes || undefined === attributes [ attribute ] ) {
187
+ if ( undefined === attributes || undefined === attributes [ attribute ] ) {
168
188
return ;
169
189
}
170
190
171
191
let oldString : string = attributes [ attribute ] . text || attributes [ attribute ] ;
172
- let newString : string = oldString . replace ( args . pattern , ( ) => {
173
- setReplacements ( ( items ) => items + 1 ) ;
192
+ let newString : string = oldString . replace ( args . pattern , ( ) => {
193
+ setReplacements ( ( items ) => items + 1 ) ;
174
194
return args . text ;
175
- } ) ;
195
+ } ) ;
176
196
177
- if ( newString === oldString ) {
197
+ if ( newString === oldString ) {
178
198
return ;
179
199
}
180
200
181
201
const property = { } ;
182
202
property [ attribute ] = newString ;
183
203
184
- ( dispatch ( 'core/block-editor' ) as any ) . updateBlockAttributes ( clientId , property ) ;
204
+ if ( args . context ) {
205
+ ( dispatch ( 'core/block-editor' ) as any )
206
+ . updateBlockAttributes ( clientId , property ) ;
207
+ }
185
208
186
209
// Handle edge-case ('value') with Pullquotes.
187
- if ( attributes . value ) {
188
- ( dispatch ( 'core/block-editor' ) as any )
189
- . updateBlockAttributes ( clientId , { value : newString } ) ;
190
- setReplacements ( ( items ) => items + 1 ) ;
210
+ if ( attributes . value ) {
211
+ if ( args . context ) {
212
+ ( dispatch ( 'core/block-editor' ) as any )
213
+ . updateBlockAttributes ( clientId , { value : newString } ) ;
214
+ }
215
+ setReplacements ( ( items ) => items + 1 ) ;
191
216
}
192
217
}
193
218
@@ -201,7 +226,7 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
201
226
*
202
227
* @returns {void }
203
228
*/
204
- useEffect ( ( ) => {
229
+ useEffect ( ( ) => {
205
230
const editor = getBlockEditorIframe ( ) ;
206
231
207
232
editor . addEventListener (
@@ -213,48 +238,48 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
213
238
'selectionchange' , handleSelection
214
239
) ;
215
240
} ;
216
- } , [ ] ) ;
241
+ } , [ ] ) ;
217
242
218
243
return (
219
244
< >
220
- < Shortcut onKeyDown = { openModal } />
221
- < Tooltip text = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) } >
245
+ < Shortcut onKeyDown = { openModal } />
246
+ < Tooltip text = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) } >
222
247
< Button
223
248
icon = { search }
224
- label = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) }
225
- onClick = { openModal }
249
+ label = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) }
250
+ onClick = { openModal }
226
251
/>
227
252
</ Tooltip >
228
253
{
229
254
isModalVisible && (
230
255
< Modal
231
- title = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) }
232
- onRequestClose = { closeModal }
256
+ title = { __ ( 'Search & Replace' , 'search-replace-for-block-editor' ) }
257
+ onRequestClose = { closeModal }
233
258
className = "search-replace-modal"
234
259
>
235
260
< div id = "search-replace-modal__text-group" >
236
261
< TextControl
237
262
type = "text"
238
- label = { __ ( 'Search' ) }
239
- value = { searchInput }
240
- onChange = { ( value ) => setSearchInput ( value ) }
263
+ label = { __ ( 'Search' ) }
264
+ value = { searchInput }
265
+ onChange = { ( value ) => setSearchInput ( value ) }
241
266
placeholder = "Lorem ipsum..."
242
267
__nextHasNoMarginBottom
243
268
/>
244
269
< TextControl
245
270
type = "text"
246
- label = { __ ( 'Replace' ) }
247
- value = { replaceInput }
248
- onChange = { ( value ) => setReplaceInput ( value ) }
271
+ label = { __ ( 'Replace' ) }
272
+ value = { replaceInput }
273
+ onChange = { ( value ) => setReplaceInput ( value ) }
249
274
__nextHasNoMarginBottom
250
275
/>
251
276
</ div >
252
277
253
278
< div id = "search-replace-modal__toggle" >
254
279
< ToggleControl
255
- label = { __ ( 'Match Case | Expression' , 'search-replace-for-block-editor' ) }
256
- checked = { caseSensitive }
257
- onChange = { handleCaseSensitive }
280
+ label = { __ ( 'Match Case | Expression' , 'search-replace-for-block-editor' ) }
281
+ checked = { caseSensitive }
282
+ onChange = { handleCaseSensitive }
258
283
__nextHasNoMarginBottom
259
284
/>
260
285
</ div >
@@ -263,7 +288,15 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
263
288
replacements ? (
264
289
< div id = "search-replace-modal__notification" >
265
290
< p >
266
- < strong > { replacements } </ strong > { __ ( 'item(s) replaced successfully' , 'search-replace-for-block-editor' ) } .
291
+ { context ? (
292
+ < >
293
+ < strong > { replacements } </ strong > { __ ( 'item(s) replaced successfully' , 'search-replace-for-block-editor' ) } .
294
+ </ >
295
+ ) : (
296
+ < >
297
+ < strong > { replacements } </ strong > { __ ( 'item(s) found' , 'search-replace-for-block-editor' ) } .
298
+ </ >
299
+ ) }
267
300
</ p >
268
301
</ div >
269
302
) : ''
@@ -272,15 +305,15 @@ const SearchReplaceForBlockEditor = (): JSX.Element => {
272
305
< div id = "search-replace-modal__button-group" >
273
306
< Button
274
307
variant = "primary"
275
- onClick = { replace }
308
+ onClick = { ( ) => replace ( true ) }
276
309
>
277
- { __ ( 'Replace' ) }
310
+ { __ ( 'Replace' ) }
278
311
</ Button >
279
312
< Button
280
313
variant = "secondary"
281
- onClick = { closeModal }
314
+ onClick = { closeModal }
282
315
>
283
- { __ ( 'Done' ) }
316
+ { __ ( 'Done' ) }
284
317
</ Button >
285
318
</ div >
286
319
</ Modal >
0 commit comments