@@ -15,8 +15,8 @@ import Button from 'components/Button';
1515import ToggleElementButton from 'components/ModularComponents/ToggleElementButton' ;
1616import Spinner from '../Spinner' ;
1717import SearchOptionsFlyout from './SearchOptionsFlyout' ;
18- import { getInstanceNode } from 'helpers/getRootNode ' ;
19- import { isOfficeEditorMode } from 'helpers/officeEditor' ;
18+ import { getEndFacingChevronIcon , getStartFacingChevronIcon } from 'helpers/rightToLeft ' ;
19+ import { isOfficeEditorMode , isSpreadsheetEditorMode } from 'helpers/officeEditor' ;
2020import './SearchOverlay.scss' ;
2121import '../Button/Button.scss' ;
2222
@@ -40,24 +40,29 @@ const propTypes = {
4040 selectPreviousResult : PropTypes . func ,
4141 isProcessingSearchResults : PropTypes . bool ,
4242 activeDocumentViewerKey : PropTypes . number ,
43+ showReplaceSpinner : PropTypes . bool ,
44+ setShowReplaceSpinner : PropTypes . func ,
4345} ;
4446
4547function SearchOverlay ( props ) {
4648 const { t } = useTranslation ( ) ;
4749 const { isSearchOverlayDisabled, searchResults, activeResultIndex, selectNextResult, selectPreviousResult, isProcessingSearchResults, activeDocumentViewerKey } = props ;
4850 const { searchValue, setSearchValue, executeSearch, replaceValue, nextResultValue, setReplaceValue } = props ;
49- const { isCaseSensitive, setCaseSensitive, isWholeWord, setWholeWord, isWildcard, setWildcard, setSearchStatus, isSearchInProgress , setIsSearchInProgress } = props ;
51+ const { isCaseSensitive, setCaseSensitive, isWholeWord, setWholeWord, isWildcard, setWildcard, setSearchStatus } = props ;
5052 const { searchStatus, isPanelOpen } = props ;
53+ const { showReplaceSpinner, setShowReplaceSpinner } = props ;
5154 const [ isReplaceBtnDisabled , setReplaceBtnDisabled ] = useState ( true ) ;
5255 const [ isReplaceAllBtnDisabled , setReplaceAllBtnDisabled ] = useState ( true ) ;
53- const [ showReplaceSpinner , setShowReplaceSpinner ] = useState ( false ) ;
5456 const [ isReplacementRegexValid , setReplacementRegexValid ] = useState ( true ) ;
5557 const [ allowInitialSearch , setAllowInitialSearch ] = useState ( false ) ;
5658 const [ isReplaceInputActive , setisReplaceInputActive ] = useState ( false ) ;
5759 const isSearchAndReplaceDisabled = useSelector ( ( state ) => selectors . isElementDisabled ( state , 'searchAndReplace' ) ) ;
5860 const customizableUI = useSelector ( ( state ) => selectors . getFeatureFlags ( state ) ?. customizableUI ) ;
5961 const searchTextInputRef = useRef ( ) ;
6062 const waitTime = 300 ; // Wait time in milliseconds
63+ const dispatch = useDispatch ( ) ;
64+ const isSearchInProgress = useSelector ( ( state ) => selectors . isSearchInProgress ( state ) ) ;
65+ const officeEditorIsReplaceInProgress = useSelector ( ( state ) => selectors . getOfficeEditorIsReplaceInProgress ( state ) ) ;
6166
6267 useEffect ( ( ) => {
6368 try {
@@ -72,6 +77,9 @@ function SearchOverlay(props) {
7277 if ( numberOfResultsFound > 0 ) {
7378 setSearchStatus ( 'SEARCH_DONE' ) ;
7479 }
80+
81+ setReplaceBtnDisabled ( numberOfResultsFound === 0 ) ;
82+ setReplaceAllBtnDisabled ( numberOfResultsFound === 0 ) ;
7583 } , [ searchResults ] ) ;
7684
7785 useEffect ( ( ) => {
@@ -102,28 +110,15 @@ function SearchOverlay(props) {
102110 }
103111 } , [ isCaseSensitive , isWholeWord , isWildcard , activeDocumentViewerKey ] ) ;
104112
105- useEffect ( ( ) => {
106- core . addEventListener ( 'pagesUpdated' , onPagesUpdated ) ;
107- return ( ) => {
108- core . removeEventListener ( 'pagesUpdated' , onPagesUpdated ) ;
109- } ;
110- } ) ;
111-
112- const onPagesUpdated = ( e ) => {
113- if ( e . linearizedUpdate ) {
114- return ;
115- }
116- search ( searchValue ) ;
117- } ;
118-
119113 const search = async ( searchValue ) => {
120114 if ( searchValue && searchValue . length > 0 ) {
121- setIsSearchInProgress ( true ) ;
115+ dispatch ( actions . setSearchInProgress ( true ) ) ;
122116 setSearchStatus ( 'SEARCH_IN_PROGRESS' ) ;
123117
124118 if ( isOfficeEditorMode ( ) ) {
125119 await core . getDocument ( ) . getOfficeEditor ( ) . updateSearchData ( ) ;
126120 }
121+
127122 executeSearch ( searchValue , {
128123 caseSensitive : isCaseSensitive ,
129124 wholeWord : isWholeWord ,
@@ -139,13 +134,42 @@ function SearchOverlay(props) {
139134 [ isCaseSensitive , isWholeWord , isWildcard ]
140135 ) ;
141136
142- const throttleSearch = useCallback (
143- throttle ( search , waitTime ) ,
144- [ isCaseSensitive , isWholeWord , isWildcard ]
145- ) ;
137+ const throttleSearch = throttle ( search , waitTime ) ;
138+ const searchParamsRef = useRef ( {
139+ searchValue,
140+ throttleSearch,
141+ officeEditorIsReplaceInProgress
142+ } ) ;
143+
144+ useEffect ( ( ) => {
145+ searchParamsRef . current = {
146+ searchValue,
147+ throttleSearch,
148+ officeEditorIsReplaceInProgress
149+ } ;
150+ } , [ searchValue , throttleSearch , officeEditorIsReplaceInProgress , isCaseSensitive , isWholeWord , isWildcard ] ) ;
151+
152+ useEffect ( ( ) => {
153+ core . addEventListener ( 'pagesUpdated' , onPagesUpdated ) ;
154+ return ( ) => {
155+ core . removeEventListener ( 'pagesUpdated' , onPagesUpdated ) ;
156+ } ;
157+ } ) ;
158+
159+ const onPagesUpdated = ( e ) => {
160+ const { searchValue, officeEditorIsReplaceInProgress } = searchParamsRef . current ;
161+ if ( e . linearizedUpdate || officeEditorIsReplaceInProgress ) {
162+ return ;
163+ }
164+ search ( searchValue ) ;
165+ } ;
146166
147167 useEffect ( ( ) => {
148168 const onOfficeDocumentEdited = ( ) => {
169+ const { searchValue, throttleSearch, officeEditorIsReplaceInProgress } = searchParamsRef . current ;
170+ if ( officeEditorIsReplaceInProgress ) {
171+ return ;
172+ }
149173 if ( searchValue && searchValue . length > 0 ) {
150174 throttleSearch ( searchValue ) ;
151175 }
@@ -156,21 +180,21 @@ function SearchOverlay(props) {
156180 return ( ) => {
157181 core . getDocument ( ) ?. removeEventListener ( 'officeDocumentEdited' , onOfficeDocumentEdited ) ;
158182 } ;
159- } , [ searchValue ] ) ;
183+ } , [ ] ) ;
160184
161185 const textInputOnChange = ( event ) => {
162186 setSearchValue ( event . target . value ) ;
163187 debouncedSearch ( event . target . value ) ;
164188
165- if ( event . target . value && replaceValue ) {
189+ if ( event . target . value && numberOfResultsFound > 0 ) {
166190 setReplaceBtnDisabled ( false ) ;
167191 setReplaceAllBtnDisabled ( false ) ;
168192 }
169193 } ;
170194
171195 const replaceTextInputOnChange = ( event ) => {
172196 setReplaceValue ( event . target . value ) ;
173- if ( event . target . value && searchValue ) {
197+ if ( event . target . value && searchValue && numberOfResultsFound > 0 ) {
174198 setReplaceBtnDisabled ( false ) ;
175199 setReplaceAllBtnDisabled ( false ) ;
176200 }
@@ -224,46 +248,64 @@ function SearchOverlay(props) {
224248 [ selectPreviousResult , searchResults , activeResultIndex ] ,
225249 ) ;
226250
251+ const toggleReplaceInput = ( ) => {
252+ setisReplaceInputActive ( ! isReplaceInputActive ) ;
253+ } ;
254+
255+ const retriggerSearch = ( ) => {
256+ if ( isOfficeEditorMode ( ) ) {
257+ search ( searchValue ) ;
258+ return ;
259+ }
260+
261+ if ( isSpreadsheetEditorMode ( ) ) {
262+ const previousSearchValue = searchValue ;
263+ const previousReplaceValue = replaceValue ;
264+ clearSearchResult ( ) ;
265+ setSearchValue ( previousSearchValue ) ;
266+ setReplaceValue ( previousReplaceValue ) ;
267+
268+ const shouldEnableReplaceButtons = core . getPageSearchResults ( ) ?. length > 0 ;
269+ if ( shouldEnableReplaceButtons ) {
270+ setReplaceBtnDisabled ( false ) ;
271+ setReplaceAllBtnDisabled ( false ) ;
272+ }
273+ }
274+ } ;
275+
227276 const searchAndReplaceAll = useCallback (
228277 async function searchAndReplaceAllCallback ( ) {
229278 if ( isReplaceAllBtnDisabled && nextResultValue ) {
230279 return ;
231280 }
281+
232282 setShowReplaceSpinner ( true ) ;
233- await getInstanceNode ( ) . instance . Core . ContentEdit . searchAndReplaceText ( {
234- documentViewer : getInstanceNode ( ) . instance . Core . documentViewer ,
235- searchResults : core . getPageSearchResults ( ) ,
236- replaceWith : replaceValue ,
237- } ) ;
283+ const results = core . getPageSearchResults ( ) ;
284+ const documentViewer = core . getDocumentViewer ( ) ;
285+ await documentViewer . replace ( results , replaceValue ) ;
286+ retriggerSearch ( ) ;
238287 setShowReplaceSpinner ( false ) ;
288+ setReplaceAllBtnDisabled ( true ) ;
239289 } ,
240290 [ replaceValue ]
241291 ) ;
242292
243- const toggleReplaceInput = ( ) => {
244- setisReplaceInputActive ( ! isReplaceInputActive ) ;
245- } ;
246-
247293 const searchAndReplaceOne = useCallback (
248294 async function searchAndReplaceOneCallback ( ) {
249295 if ( isReplaceBtnDisabled && nextResultValue ) {
250296 return ;
251297 }
252- setShowReplaceSpinner ( true ) ;
253-
254- await getInstanceNode ( ) . instance . Core . ContentEdit . searchAndReplaceText ( {
255- documentViewer : getInstanceNode ( ) . instance . Core . documentViewer ,
256- replaceWith : replaceValue ,
257- searchResults : [ core . getActiveSearchResult ( ) ] ,
258- } ) ;
259298
299+ setShowReplaceSpinner ( true ) ;
300+ const activeSearchResult = core . getActiveSearchResult ( ) ;
301+ const documentViewer = core . getDocumentViewer ( ) ;
302+ await documentViewer . replace ( [ activeSearchResult ] , replaceValue ) ;
303+ retriggerSearch ( ) ;
260304 setShowReplaceSpinner ( false ) ;
261305 } ,
262306 [ replaceValue , nextResultValue , isReplaceBtnDisabled ]
263307 ) ;
264308
265- const dispatch = useDispatch ( ) ;
266-
267309 const replaceAllConfirmationWarning = ( ) => {
268310 const title = t ( 'option.searchPanel.replaceText' ) ;
269311 const message = t ( 'option.searchPanel.confirmMessageReplaceAll' ) ;
@@ -359,7 +401,7 @@ function SearchOverlay(props) {
359401 </ div >
360402 </ div >
361403 { shouldShowReplaceInput && (
362- < div data-element = "searchAndReplace" className = "replace-options" >
404+ < div data-element = { DataElements . SEARCH_PANEL_REPLACE_CONTAINER } className = "replace-options" >
363405 < div className = "input-container with-replace-icon" >
364406 < Icon
365407 disabled = { false }
@@ -373,13 +415,22 @@ function SearchOverlay(props) {
373415 />
374416 </ div >
375417 < div className = 'replace-buttons' >
376- { ( showReplaceSpinner ) ? < Spinner width = { 25 } height = { 25 } /> : null }
377- < button className = 'Button btn-replace-all' disabled = { isReplaceAllBtnDisabled }
378- aria-label = { t ( 'option.searchPanel.replaceAll' ) }
379- onClick = { replaceAllConfirmationWarning } > { t ( 'option.searchPanel.replaceAll' ) } </ button >
380- < button className = 'Button btn-replace' disabled = { isReplaceBtnDisabled || ! nextResultValue || ! core . getActiveSearchResult ( ) }
381- aria-label = { t ( 'option.searchPanel.replace' ) }
382- onClick = { replaceOneConfirmationWarning } > { t ( 'option.searchPanel.replace' ) } </ button >
418+ { ( showReplaceSpinner ) ? < Spinner width = { '25px' } height = { '25px' } /> : null }
419+ < Button
420+ onClick = { replaceAllConfirmationWarning }
421+ title = { t ( 'option.searchPanel.replaceAll' ) }
422+ ariaLabel = { t ( 'option.searchPanel.replaceAll' ) }
423+ className = { 'btn-replace-all' }
424+ disabled = { isReplaceAllBtnDisabled }
425+ > { t ( 'option.searchPanel.replaceAll' ) } </ Button >
426+
427+ < Button
428+ onClick = { replaceOneConfirmationWarning }
429+ title = { t ( 'option.searchPanel.replace' ) }
430+ ariaLabel = { t ( 'option.searchPanel.replace' ) }
431+ className = { 'btn-replace' }
432+ disabled = { isReplaceBtnDisabled || ! nextResultValue || ! core . getActiveSearchResult ( ) }
433+ > { t ( 'option.searchPanel.replace' ) } </ Button >
383434 </ div >
384435 </ div >
385436 ) }
@@ -398,11 +449,11 @@ function SearchOverlay(props) {
398449 < p className = "no-margin" aria-live = "assertive" > { isSearchDoneAndNotProcessingResults && ! isSearchInProgress ? `${ numberOfResultsFound } ${ t ( 'message.numResultsFound' ) } ` : undefined } </ p >
399450 { numberOfResultsFound > 0 && (
400451 < div className = "buttons" >
401- < button className = "button" onClick = { previousButtonOnClick } aria-label = { t ( 'action.prevResult' ) } >
402- < Icon className = "arrow" glyph = "icon-chevron-left" />
452+ < button className = "button" onClick = { previousButtonOnClick } title = { t ( 'action.prevResult' ) } aria-label = { t ( 'action.prevResult' ) } >
453+ < Icon className = "arrow" glyph = { getStartFacingChevronIcon ( ) } />
403454 </ button >
404- < button className = "button" onClick = { nextButtonOnClick } aria-label = { t ( 'action.nextResult' ) } >
405- < Icon className = "arrow" glyph = "icon-chevron-right" />
455+ < button className = "button" onClick = { nextButtonOnClick } title = { t ( 'action.nextResult' ) } aria-label = { t ( 'action.nextResult' ) } >
456+ < Icon className = "arrow" glyph = { getEndFacingChevronIcon ( ) } />
406457 </ button >
407458 </ div >
408459 ) }
0 commit comments