@@ -5,13 +5,6 @@ import { getHints } from './hints'
55import { ELEMENT_FILTER , INPUT_TEXTAREA_FILTER , RICH_TEXT_EDITORS } from './constants'
66import { getFlags , getSearchPattern } from './regex'
77
8- function replaceInInnerHTML ( element : HTMLElement , searchPattern : RegExp , replaceTerm : string ) {
9- const searchStr = element . innerHTML
10- element . innerHTML = searchStr . replace ( searchPattern , replaceTerm )
11- element . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) )
12- return ! ( element . innerHTML === searchStr )
13- }
14-
158function setNativeValue ( element : HTMLInputElement | HTMLTextAreaElement , value : string ) {
169 const valueFn = Object . getOwnPropertyDescriptor ( element , 'value' )
1710 let valueSetter : ( ( v : any ) => void ) | undefined
@@ -297,34 +290,40 @@ function replaceVisibleOnly(
297290
298291// Custom Functions
299292
300- async function cmsEditor (
301- document : Document ,
293+ async function replaceInEditorContainers (
302294 searchPattern : RegExp ,
303295 replaceTerm : string ,
304296 flags : string ,
305- richTextEditor : RichTextEditor
297+ richTextEditor : RichTextEditor ,
298+ containers : ( Element | Document ) [ ]
306299) : Promise < boolean > {
307300 let replaced = false
308301 try {
309- if ( richTextEditor . container && richTextEditor . container . iframe ) {
310- const containerOuter = document . querySelector ( richTextEditor . container . value )
311- // if container is an iframe use it, otherwise search inside for an iframe
312- const container : HTMLIFrameElement | null | undefined =
313- containerOuter && containerOuter ?. tagName === 'IFRAME'
314- ? < HTMLIFrameElement > containerOuter
315- : < HTMLIFrameElement > containerOuter ?. querySelector ( 'iframe' )
316- const editor : HTMLElement | null | undefined = container ?. contentDocument ?. querySelector (
317- richTextEditor . editor . value
318- )
319- if ( editor ) {
320- replaced = replaceInInnerHTML ( editor , searchPattern , replaceTerm )
302+ // Loop to select editor elements inside their containers
303+ for ( const containerOuter of containers ) {
304+ let container = containerOuter
305+
306+ if ( 'contentDocument' in containerOuter && containerOuter . contentDocument !== null ) {
307+ // container is an iframe use its contentDocument
308+ container = < Document > containerOuter . contentDocument
309+ } else if (
310+ richTextEditor . container &&
311+ richTextEditor . container . iframe &&
312+ 'tagName' in containerOuter &&
313+ containerOuter ?. tagName !== 'IFRAME'
314+ ) {
315+ // container contains an iframe so use that iframe and its contentDocument
316+ const innerIframe = containerOuter ?. querySelector ( 'iframe' )
317+ if ( innerIframe !== null && innerIframe . contentDocument !== null ) {
318+ container = innerIframe . contentDocument
319+ }
320+ }
321+
322+ const editors = Array . from ( container . querySelectorAll ( richTextEditor . editor . value . join ( ',' ) ) || [ ] )
323+ replaced = await replaceInEditors ( searchPattern , replaceTerm , editors , flags )
324+ if ( replaceNextOnly ( flags ) && replaced ) {
325+ return replaced
321326 }
322- } else {
323- const editor = < HTMLElement > document . querySelector ( richTextEditor . editor . value )
324- const initialText = editor . textContent || ''
325- const newText = initialText . replace ( searchPattern , replaceTerm )
326- await replaceInContentEditableElement ( editor , initialText , newText )
327- replaced = initialText !== newText
328327 }
329328 } catch ( err ) {
330329 console . error ( err )
@@ -334,54 +333,75 @@ async function cmsEditor(
334333 return replaced
335334}
336335
337- // taken from https://stackoverflow.com/a/69656905/1178971
338- async function replaceInContentEditableElement (
339- element : HTMLElement ,
340- initialText : string ,
341- replacementText : string
336+ async function replaceInEditors (
337+ searchPattern : RegExp ,
338+ replaceTerm : string ,
339+ editors : Element [ ] ,
340+ flags : string
342341) : Promise < boolean > {
343- return new Promise ( ( resolve ) => {
344- // select the content editable area
345- element . dispatchEvent ( new FocusEvent ( 'focus' , { bubbles : true } ) )
346- if ( element . innerText === element . innerHTML ) {
347- element . textContent = replacementText
348- } else {
349- element . innerHTML = element . innerHTML . replace ( initialText , replacementText )
342+ let replaced = false
343+ for ( const editor of editors ) {
344+ const newReplaced = replaceInInnerHTML ( editor as HTMLElement , searchPattern , replaceTerm )
345+ replaced = replaced || newReplaced
346+ if ( replaceNextOnly ( flags ) && replaced ) {
347+ return replaced
350348 }
349+ }
350+ return replaced
351+ }
351352
352- element . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) )
353-
354- resolve ( element . innerText !== initialText )
355- } )
353+ function replaceInInnerHTML ( element : HTMLElement | Element , searchPattern : RegExp , replaceTerm : string ) : boolean {
354+ // select the content editable area
355+ element . dispatchEvent ( new FocusEvent ( 'focus' , { bubbles : true } ) )
356+ const initialText = getTextContent ( element )
357+ const initialHTML = element . innerHTML
358+ if ( 'innerText' in element && element . innerText === element . innerHTML ) {
359+ element . textContent = element . innerText . replace ( searchPattern , replaceTerm )
360+ } else {
361+ element . innerHTML = element . innerHTML . replace ( searchPattern , replaceTerm )
362+ }
363+ element . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) )
364+ return ( 'innerText' in element && element . innerText !== initialText ) || element . innerHTML !== initialHTML
356365}
357366
358- function selectElementContents ( window : Window , el : HTMLElement ) {
359- const range = window . document . createRange ( )
360- range . selectNodeContents ( el )
361- const sel = window . getSelection ( )
362- if ( sel ) {
363- sel . removeAllRanges ( )
364- sel . addRange ( range )
367+ function getTextContent ( element : HTMLElement | Element ) : string {
368+ if ( element . textContent ) {
369+ return element . textContent
370+ } else if ( 'innerText' in element && element . innerText ) {
371+ return element . innerText
365372 }
373+ return ''
366374}
367375
368376async function replaceInCMSEditors (
369377 document : Document ,
370378 searchPattern : RegExp ,
371379 replaceTerm : string ,
372- flags : string ,
373- visibleOnly : boolean
380+ flags : string
374381) : Promise < boolean > {
375382 let replaced = false
376383 // replacement functions for pages with text editors
377384 for ( const richTextEditor of RICH_TEXT_EDITORS ) {
378385 if ( richTextEditor . container ) {
379- if ( document . querySelectorAll ( richTextEditor . container . value ) . length ) {
380- replaced = await cmsEditor ( document , searchPattern , replaceTerm , flags , richTextEditor )
386+ const containers = Array . from ( document . querySelectorAll ( richTextEditor . container . value . join ( ',' ) ) )
387+ if ( containers . length ) {
388+ replaced = await replaceInEditorContainers (
389+ searchPattern ,
390+ replaceTerm ,
391+ flags ,
392+ richTextEditor ,
393+ containers
394+ )
395+ if ( replaceNextOnly ( flags ) && replaced ) {
396+ return replaced
397+ }
381398 }
382399 } else {
383- if ( document . querySelectorAll ( richTextEditor . editor . value ) . length ) {
384- replaced = await cmsEditor ( document , searchPattern , replaceTerm , flags , richTextEditor )
400+ const editors = Array . from ( document . querySelectorAll ( richTextEditor . editor . value . join ( ',' ) ) )
401+ replaced = await replaceInEditors ( searchPattern , replaceTerm , editors , flags )
402+ document . body . dispatchEvent ( new Event ( 'input' , { bubbles : true } ) )
403+ if ( replaceNextOnly ( flags ) && replaced ) {
404+ return replaced
385405 }
386406 }
387407 }
@@ -404,7 +424,7 @@ export async function searchReplace(
404424 let replaced = false
405425
406426 // replacement functions for pages with text editors
407- replaced = await replaceInCMSEditors ( document , searchPattern , replaceTerm , flags , visibleOnly )
427+ replaced = await replaceInCMSEditors ( document , searchPattern , replaceTerm , flags )
408428
409429 if ( replaceNextOnly ( flags ) && replaced ) {
410430 return replaced
@@ -415,14 +435,7 @@ export async function searchReplace(
415435 const iframes = getIframeElements ( document )
416436 for ( const iframe of iframes ) {
417437 if ( iframe . src . match ( '^http://' + window . location . host ) || ! iframe . src . match ( '^https?' ) ) {
418- const richTextEditors = RICH_TEXT_EDITORS . filter ( ( editor ) => editor . container ?. iframe )
419- replaced = await replaceInCMSEditors (
420- iframe . contentDocument ! ,
421- searchPattern ,
422- replaceTerm ,
423- flags ,
424- visibleOnly
425- )
438+ replaced = await replaceInCMSEditors ( iframe . contentDocument ! , searchPattern , replaceTerm , flags )
426439 if ( replaceNextOnly ( flags ) && replaced ) {
427440 return replaced
428441 }
0 commit comments