PluginProbe
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More / 6.11
Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More v6.11
6.35 6.34 6.33.1 6.33 6.32.1 6.32 6.31 6.25 6.25.1 6.26 6.26.1 6.27 6.28 6.29 6.3 6.3.1 6.3.2 6.30 6.4 6.4.1 6.4.2 6.5 6.5.1 6.5.2 6.5.3 All 141 releases
formidable / js / admin / dom.js

dom.js in Formidable Forms – WordPress Form Builder for Contact Forms, Calculators, Quizzes & More 6.11, at js/admin/dom.js

859 lines 23.3 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 ( function() {
2 /** globals frmGlobal */
3
4 let __;
5
6 if ( 'undefined' === typeof wp || 'undefined' === typeof wp.i18n || 'function' !== typeof wp.i18n.__ ) {
7 __ = text => text;
8 } else {
9 __ = wp.i18n.__;
10 }
11
12 const modal = {
13 maybeCreateModal: ( id, { title, content, footer, width } = {}) => {
14 let modal = document.getElementById( id );
15
16 if ( ! modal ) {
17 modal = createEmptyModal( id );
18
19 const titleElement = div({
20 className: 'frm-modal-title'
21 });
22
23 if ( 'string' === typeof title ) {
24 titleElement.textContent = title;
25 }
26
27 const a = tag(
28 'a',
29 {
30 child: svg({ href: '#frm_close_icon' }),
31 className: 'dismiss'
32 }
33 );
34 const postbox = modal.querySelector( '.postbox' );
35
36 postbox.appendChild(
37 div({
38 className: 'frm_modal_top',
39 children: [
40 titleElement,
41 div({ child: a })
42 ]
43 })
44 );
45 postbox.appendChild(
46 div({ className: 'frm_modal_content' })
47 );
48
49 if ( footer ) {
50 postbox.appendChild(
51 div({ className: 'frm_modal_footer' })
52 );
53 }
54 } else if ( 'string' === typeof title ) {
55 const titleElement = modal.querySelector( '.frm-modal-title' );
56 titleElement.textContent = title;
57 }
58
59 if ( ! content && ! footer ) {
60 makeModalIntoADialogAndOpen( modal, { width });
61 return modal;
62 }
63
64 const postbox = modal.querySelector( '.postbox' );
65 const modalHelper = getModalHelper( modal, postbox );
66
67 if ( content ) {
68 modalHelper( content, 'frm_modal_content' );
69 }
70
71 if ( footer ) {
72 modalHelper( footer, 'frm_modal_footer' );
73 }
74
75 makeModalIntoADialogAndOpen( modal );
76 return modal;
77 },
78 footerButton: args => {
79 const output = a( args );
80 output.setAttribute( 'role', 'button' );
81 output.setAttribute( 'tabindex', 0 );
82 if ( args.buttonType ) {
83 output.classList.add( 'button' );
84
85 if ( ! args.noDismiss && -1 !== [ 'red', 'primary' ].indexOf( args.buttonType ) ) {
86 // Primary and red buttons close modals by default on click.
87 // To disable this default behaviour you can use the noDismiss: 1 arg.
88 output.classList.add( 'dismiss' );
89 }
90
91 switch ( args.buttonType ) {
92 case 'red':
93 output.classList.add( 'frm-button-red', 'frm-button-primary' );
94 break;
95 case 'primary':
96 output.classList.add( 'button-primary', 'frm-button-primary' );
97 break;
98 case 'secondary':
99 output.classList.add( 'button-secondary', 'frm-button-secondary' );
100 output.style.marginRight = '10px';
101 break;
102 case 'cancel':
103 output.classList.add( 'button-secondary', 'frm-modal-cancel' );
104 break;
105 }
106 }
107 return output;
108 }
109 };
110
111 const ajax = {
112 doJsonFetch: async function( action ) {
113 let targetUrl = ajaxurl + '?action=frm_' + action;
114 if ( -1 === targetUrl.indexOf( 'nonce=' ) ) {
115 targetUrl += '&nonce=' + frmGlobal.nonce;
116 }
117 const response = await fetch( targetUrl );
118 const json = await response.json();
119 if ( ! json.success ) {
120 return Promise.reject( json.data || 'JSON result is not successful' );
121 }
122 return Promise.resolve( json.data );
123 },
124 doJsonPost: async function( action, formData, { signal } = {}) {
125 formData.append( 'nonce', frmGlobal.nonce );
126 const init = {
127 method: 'POST',
128 body: formData
129 };
130 if ( signal ) {
131 init.signal = signal;
132 }
133 const response = await fetch( ajaxurl + '?action=frm_' + action, init );
134 const json = await response.json();
135 if ( ! json.success ) {
136 return Promise.reject( json.data || 'JSON result is not successful' );
137 }
138 return Promise.resolve( 'undefined' !== typeof json.data ? json.data : json );
139 }
140 };
141
142 const multiselect = {
143 init: function() {
144 const $select = jQuery( this );
145 const id = $select.is( '[id]' ) ? $select.attr( 'id' ).replace( '[]', '' ) : false;
146
147 let labelledBy = id ? jQuery( '#for_' + id ) : false;
148 labelledBy = id && labelledBy.length ? 'aria-labelledby="' + labelledBy.attr( 'id' ) + '"' : '';
149
150 // Set empty title attributes so that none of the dropdown options include title attributes.
151 $select.find( 'option' ).attr( 'title', ' ' );
152 $select.multiselect({
153 templates: {
154 popupContainer: '<div class="multiselect-container frm-dropdown-menu"></div>',
155 option: '<button type="button" class="multiselect-option dropdown-item frm_no_style_button"></button>',
156 button: '<button type="button" class="multiselect dropdown-toggle btn" data-toggle="dropdown" ' + labelledBy + '><span class="multiselect-selected-text"></span> <b class="caret"></b></button>'
157 },
158 buttonContainer: '<div class="btn-group frm-btn-group dropdown" />',
159 nonSelectedText: __( '— Select —', 'formidable' ),
160 // Prevent the dropdown from showing "All Selected" when every option is checked.
161 allSelectedText: '',
162 // This is 3 by default. We want to show more options before it starts showing a count.
163 numberDisplayed: 8,
164 onInitialized: function( _, $container ) {
165 $container.find( '.multiselect.dropdown-toggle' ).removeAttr( 'title' );
166 },
167 onDropdownShown: function( event ) {
168 const action = jQuery( event.currentTarget.closest( '.frm_form_action_settings, #frm-show-fields' ) );
169 if ( action.length ) {
170 jQuery( '#wpcontent' ).on( 'click', function() {
171 if ( jQuery( '.multiselect-container.frm-dropdown-menu' ).is( ':visible' ) ) {
172 jQuery( event.currentTarget ).removeClass( 'open' );
173 }
174 });
175 }
176
177 const $dropdown = $select.next( '.frm-btn-group.dropdown' );
178 $dropdown.find( '.dropdown-item' ).each(
179 function() {
180 const option = this;
181 const dropdownInput = option.querySelector( 'input[type="checkbox"], input[type="radio"]' );
182 if ( dropdownInput ) {
183 option.setAttribute( 'role', 'checkbox' );
184 option.setAttribute( 'aria-checked', dropdownInput.checked ? 'true' : 'false' );
185 }
186 }
187 );
188 },
189 onChange: function( $option, checked ) {
190 $select.trigger( 'frm-multiselect-changed', $option, checked );
191
192 const $dropdown = $select.next( '.frm-btn-group.dropdown' );
193 const optionValue = $option.val();
194 const $dropdownItem = $dropdown.find( 'input[value="' + optionValue + '"]' ).closest( 'button.dropdown-item' );
195 if ( $dropdownItem.length ) {
196 $dropdownItem.attr( 'aria-checked', checked ? 'true' : 'false' );
197
198 // Delay a focus event so the screen reader reads the option value again.
199 // Without this, and without the setTimeout, it only reads "checked" or "unchecked".
200 setTimeout( () => $dropdownItem.get( 0 ).focus(), 0 );
201 }
202 }
203 });
204 }
205 };
206
207 const bootstrap = {
208 setupBootstrapDropdowns( callback ) {
209 if ( ! window.bootstrap || ! window.bootstrap.Dropdown ) {
210 return;
211 }
212
213 window.bootstrap.Dropdown._getParentFromElement = getParentFromElement;
214 window.bootstrap.Dropdown.prototype._getParentFromElement = getParentFromElement;
215
216 function getParentFromElement( element ) {
217 let parent;
218 const selector = window.bootstrap.Util.getSelectorFromElement( element );
219
220 if ( selector ) {
221 parent = document.querySelector( selector );
222 }
223
224 const result = parent || element.parentNode;
225 const frmDropdownMenu = result.querySelector( '.frm-dropdown-menu' );
226
227 if ( ! frmDropdownMenu ) {
228 // Not a formidable dropdown, treat like Bootstrap does normally.
229 return result;
230 }
231
232 // Temporarily add dropdown-menu class so bootstrap can initialize.
233 frmDropdownMenu.classList.add( 'dropdown-menu' );
234 setTimeout(
235 function() {
236 frmDropdownMenu.classList.remove( 'dropdown-menu' );
237 },
238 0
239 );
240
241 if ( 'function' === typeof callback ) {
242 callback( frmDropdownMenu );
243 }
244
245 return result;
246 }
247 },
248 multiselect
249 };
250
251 const autocomplete = {
252 initSelectionAutocomplete: function() {
253 if ( jQuery.fn.autocomplete ) {
254 autocomplete.initAutocomplete( 'page' );
255 autocomplete.initAutocomplete( 'user' );
256 }
257 },
258 /**
259 * Init autocomplete.
260 *
261 * @since 4.10.01 Add container param to init autocomplete elements inside an element.
262 *
263 * @param {String} type Type of data. Accepts `page` or `user`.
264 * @param {String|Object} container Container class or element. Default is null.
265 */
266 initAutocomplete: function( type, container ) {
267 const basedUrlParams = '?action=frm_' + type + '_search&nonce=' + frmGlobal.nonce;
268 const elements = ! container ? jQuery( '.frm-' + type + '-search' ) : jQuery( container ).find( '.frm-' + type + '-search' );
269
270 elements.each( initAutocompleteForElement );
271
272 function initAutocompleteForElement() {
273 let urlParams = basedUrlParams;
274 const element = jQuery( this );
275
276 // Check if a custom post type is specific.
277 if ( element.attr( 'data-post-type' ) ) {
278 urlParams += '&post_type=' + element.attr( 'data-post-type' );
279 }
280
281 element.autocomplete({
282 delay: 100,
283 minLength: 0,
284 source: ajaxurl + urlParams,
285 change: autocomplete.selectBlank,
286 select: autocomplete.completeSelectFromResults,
287 focus: () => false,
288 position: {
289 my: 'left top',
290 at: 'left bottom',
291 collision: 'flip'
292 },
293 response: function( event, ui ) {
294 if ( ! ui.content.length ) {
295 const noResult = {
296 value: '',
297 label: frm_admin_js.no_items_found
298 };
299 ui.content.push( noResult );
300 }
301 },
302 create: function() {
303 let $container = jQuery( this ).parent();
304
305 if ( $container.length === 0 ) {
306 $container = 'body';
307 }
308
309 jQuery( this ).autocomplete( 'option', 'appendTo', $container );
310 }
311 })
312 .on( 'focus', function() {
313 // Show options on click to make it work more like a dropdown.
314 if ( this.value === '' || this.nextElementSibling.value < 1 ) {
315 jQuery( this ).autocomplete( 'search', this.value );
316 }
317 })
318 .data( 'ui-autocomplete' )._renderItem = function( ul, item ) {
319 return jQuery( '<li>' )
320 .attr( 'aria-label', item.label )
321 .append( jQuery( '<div>' ).text( item.label ) )
322 .appendTo( ul );
323 };
324 }
325 },
326
327 selectBlank: function( e, ui ) {
328 if ( ui.item === null ) {
329 this.nextElementSibling.value = '';
330 }
331 },
332
333 completeSelectFromResults: function( e, ui ) {
334 e.preventDefault();
335 this.value = ui.item.value === '' ? '' : ui.item.label;
336 this.nextElementSibling.value = ui.item.value;
337 }
338 };
339
340 const search = {
341 wrapInput: ( searchInput, labelText ) => {
342 const label = tag(
343 'label',
344 {
345 className: 'screen-reader-text',
346 text: labelText
347 }
348 );
349 label.setAttribute( 'for', searchInput.id );
350 return tag(
351 'p',
352 {
353 className: 'frm-search',
354 children: [
355 label,
356 span({ className: 'frmfont frm_search_icon' }),
357 searchInput
358 ]
359 }
360 );
361 },
362 newSearchInput: ( id, placeholder, targetClassName, args = {}) => {
363 const input = getAutoSearchInput( id, placeholder );
364 const wrappedSearch = search.wrapInput( input, placeholder );
365 search.init( input, targetClassName, args );
366
367 function getAutoSearchInput( id, placeholder ) {
368 const className = 'frm-search-input frm-auto-search frm-w-full';
369 const inputArgs = { id, className };
370 const input = tag( 'input', inputArgs );
371 input.setAttribute( 'placeholder', placeholder );
372 return input;
373 }
374
375 return wrappedSearch;
376 },
377 init: ( input, targetClassName, { handleSearchResult } = {}) => {
378 input.setAttribute( 'type', 'search' );
379 input.setAttribute( 'autocomplete', 'off' );
380
381 input.addEventListener( 'input', handleSearch );
382 input.addEventListener( 'search', handleSearch );
383 input.addEventListener( 'change', handleSearch );
384
385 function handleSearch( event ) {
386 const searchText = input.value.toLowerCase();
387 const notEmptySearchText = searchText !== '';
388 const items = Array.from( document.getElementsByClassName( targetClassName ) );
389
390 let foundSomething = false;
391 items.forEach( toggleSearchClassesForItem );
392 if ( 'function' === typeof handleSearchResult ) {
393 handleSearchResult({ foundSomething, notEmptySearchText }, event );
394 }
395
396 function toggleSearchClassesForItem( item ) {
397 let itemText;
398
399 if ( item.hasAttribute( 'frm-search-text' ) ) {
400 itemText = item.getAttribute( 'frm-search-text' );
401 } else {
402 itemText = item.innerText.toLowerCase();
403 item.setAttribute( 'frm-search-text', itemText );
404 }
405
406 const hide = notEmptySearchText && -1 === itemText.indexOf( searchText );
407 item.classList.toggle( 'frm_hidden', hide );
408
409 const isSearchResult = ! hide && notEmptySearchText;
410 if ( isSearchResult ) {
411 foundSomething = true;
412 }
413 item.classList.toggle( 'frm-search-result', isSearchResult );
414 }
415 }
416 }
417 };
418
419 const util = {
420 debounce: ( func, wait = 100 ) => {
421 let timeout;
422 return function( ...args ) {
423 clearTimeout( timeout );
424 timeout = setTimeout(
425 () => func.apply( this, args ),
426 wait
427 );
428 };
429 },
430 onClickPreventDefault: ( element, callback ) => {
431 const listener = event => {
432 event.preventDefault();
433 callback( event );
434 };
435 element?.addEventListener( 'click', listener );
436 },
437
438 /**
439 * Does the same as jQuery( document ).on( 'event', 'selector', handler ).
440 *
441 * @since 6.0
442 *
443 * @param {String} event Event name.
444 * @param {String} selector Selector.
445 * @param {Function} handler Handler.
446 * @param {Boolean|Object} options Options to be added to `addEventListener()` method. Default is `false`.
447 */
448 documentOn: ( event, selector, handler, options ) => {
449 if ( 'undefined' === typeof options ) {
450 options = false;
451 }
452
453 document.addEventListener( event, function( e ) {
454 let target;
455
456 // loop parent nodes from the target to the delegation node.
457 for ( target = e.target; target && target != this; target = target.parentNode ) {
458 if ( target && target.matches && target.matches( selector ) ) {
459 handler.call( target, e );
460 break;
461 }
462 }
463 }, options );
464 }
465 };
466
467 const wysiwyg = {
468 init( editor, { setupCallback, height, addFocusEvents } = {}) {
469 if ( isTinyMceActive() ) {
470 setTimeout( resetTinyMce, 0 );
471 } else {
472 initQuickTagsButtons();
473 }
474
475 setUpTinyMceVisualButtonListener();
476 setUpTinyMceHtmlButtonListener();
477
478 function initQuickTagsButtons() {
479 if ( 'function' !== typeof window.quicktags || typeof window.QTags.instances[ editor.id ] !== 'undefined' ) {
480 return;
481 }
482
483 const id = editor.id;
484 window.quicktags({
485 name: 'qt_' + id,
486 id: id,
487 canvas: editor,
488 settings: { id },
489 toolbar: document.getElementById( 'qt_' + id + '_toolbar' ),
490 theButtons: {}
491 });
492 }
493
494 function initRichText() {
495 const key = Object.keys( tinyMCEPreInit.mceInit )[0];
496 const orgSettings = tinyMCEPreInit.mceInit[ key ];
497
498 const settings = Object.assign(
499 {},
500 orgSettings,
501 {
502 selector: '#' + editor.id,
503 body_class: orgSettings.body_class.replace( key, editor.id )
504 }
505 );
506
507 settings.setup = editor => {
508 if ( addFocusEvents ) {
509 function focusInCallback() {
510 jQuery( editor.targetElm ).trigger( 'focusin' );
511 editor.off( 'focusin', '**' );
512 }
513
514 editor.on( 'focusin', focusInCallback );
515
516 editor.on( 'focusout', function() {
517 editor.on( 'focusin', focusInCallback );
518 });
519 }
520 if ( setupCallback ) {
521 setupCallback( editor );
522 }
523 };
524
525 if ( height ) {
526 settings.height = height;
527 }
528
529 tinymce.init( settings );
530 }
531
532 function removeRichText() {
533 tinymce.EditorManager.execCommand( 'mceRemoveEditor', true, editor.id );
534 }
535
536 function resetTinyMce() {
537 removeRichText();
538 initRichText();
539 }
540
541 function isTinyMceActive() {
542 const id = editor.id;
543 const wrapper = document.getElementById( 'wp-' + id + '-wrap' );
544 return null !== wrapper && wrapper.classList.contains( 'tmce-active' );
545 }
546
547 function setUpTinyMceVisualButtonListener() {
548 jQuery( document ).on(
549 'click', '#' + editor.id + '-html',
550 function() {
551 editor.style.visibility = 'visible';
552 initQuickTagsButtons();
553 }
554 );
555 }
556
557 function setUpTinyMceHtmlButtonListener() {
558 jQuery( '#' + editor.id + '-tmce' ).on( 'click', handleTinyMceHtmlButtonClick );
559 }
560
561 function handleTinyMceHtmlButtonClick() {
562 if ( isTinyMceActive() ) {
563 resetTinyMce();
564 } else {
565 initRichText();
566 }
567
568 const wrap = document.getElementById( 'wp-' + editor.id + '-wrap' );
569 wrap.classList.add( 'tmce-active' );
570 wrap.classList.remove( 'html-active' );
571 }
572 }
573 };
574
575 function getModalHelper( modal, appendTo ) {
576 return function( child, uniqueClassName ) {
577 let element = modal.querySelector( '.' + uniqueClassName );
578 if ( null === element ) {
579 element = div({
580 child: child,
581 className: uniqueClassName
582 });
583 appendTo.appendChild( element );
584 } else {
585 redraw( element, child );
586 }
587 };
588 }
589
590 function createEmptyModal( id ) {
591 const modal = div({ id, className: 'frm-modal' });
592 const postbox = div({ className: 'postbox' });
593 const metaboxHolder = div({ className: 'metabox-holder', child: postbox });
594 modal.appendChild( metaboxHolder );
595 document.body.appendChild( modal );
596 return modal;
597 }
598
599 function makeModalIntoADialogAndOpen( modal, { width } = {}) {
600 const bodyWithModalClassName = 'frm-body-with-open-modal';
601
602 const $modal = jQuery( modal );
603 if ( ! $modal.hasClass( 'frm-dialog' ) ) {
604 $modal.dialog({
605 dialogClass: 'frm-dialog',
606 modal: true,
607 autoOpen: false,
608 closeOnEscape: true,
609 width: width || '550px',
610 resizable: false,
611 draggable: false,
612 open: function() {
613 jQuery( '.ui-dialog-titlebar' ).addClass( 'frm_hidden' ).removeClass( 'ui-helper-clearfix' );
614 jQuery( '#wpwrap' ).addClass( 'frm_overlay' );
615 jQuery( '.frm-dialog' ).removeClass( 'ui-widget ui-widget-content ui-corner-all' );
616
617 modal.classList.remove( 'ui-dialog-content', 'ui-widget-content' );
618
619 $modal.on( 'click', 'a.dismiss', function( event ) {
620 event.preventDefault();
621 $modal.dialog( 'close' );
622 });
623
624 const overlay = document.querySelector( '.ui-widget-overlay' );
625 if ( overlay ) {
626 overlay.addEventListener(
627 'click',
628 function( event ) {
629 event.preventDefault();
630 $modal.dialog( 'close' );
631 }
632 );
633 }
634 },
635 close: function() {
636 document.body.classList.remove( bodyWithModalClassName );
637 jQuery( '#wpwrap' ).removeClass( 'frm_overlay' );
638 jQuery( '.spinner' ).css( 'visibility', 'hidden' );
639 }
640 });
641 }
642
643 document.body.classList.add( bodyWithModalClassName );
644
645 $modal.dialog( 'open' );
646 return $modal;
647 }
648
649 function div( args ) {
650 return tag( 'div', args );
651 }
652
653 function span( args ) {
654 return tag( 'span', args );
655 }
656
657 function a( args = {}) {
658 const anchor = tag( 'a', args );
659 anchor.setAttribute( 'href', 'string' === typeof args.href ? args.href : '#' );
660 if ( 'string' === typeof args.target ) {
661 anchor.target = args.target;
662 }
663 return anchor;
664 }
665
666 function img( args = {}) {
667 const output = tag( 'img', args );
668 if ( 'string' === typeof args.src ) {
669 output.setAttribute( 'src', args.src );
670 }
671 if ( 'string' === typeof args.alt ) {
672 output.setAttribute( 'alt', args.alt );
673 }
674 return output;
675 }
676
677 /**
678 * Get a labelled text input and a matching label.
679 *
680 * @since 6.0
681 *
682 * @param {String} inputId
683 * @param {String} labelText
684 * @param {String} inputName
685 * @returns {Element}
686 */
687 function labelledTextInput( inputId, labelText, inputName ) {
688 const label = tag( 'label', labelText );
689 label.setAttribute( 'for', inputId );
690
691 const input = tag(
692 'input',
693 {
694 id: inputId,
695 className: 'frm_long_input'
696 }
697 );
698 input.type = 'text';
699 input.setAttribute( 'name', inputName );
700
701 return div({ children: [ label, input ] });
702 }
703
704 /**
705 * Build an element.
706 *
707 * @since 6.4.1 Accept a string as one of `children` to append a text node inside the element.
708 *
709 * @param {String} type Element tag name.
710 * @param {Object} args The args.
711 * @return {Object}
712 */
713 function tag( type, args = {}) {
714 const output = document.createElement( type );
715
716 if ( 'string' === typeof args ) {
717 // Support passing just a string to a tag for simple text elements.
718 output.textContent = args;
719 return output;
720 }
721
722 const { id, className, children, child, text, data } = args;
723
724 if ( id ) {
725 output.id = id;
726 }
727 if ( className ) {
728 output.className = className;
729 }
730 if ( children ) {
731 children.forEach( child => {
732 if ( 'string' === typeof child ) {
733 output.appendChild( document.createTextNode( child ) );
734 } else {
735 output.appendChild( child )
736 }
737 } );
738 } else if ( child ) {
739 output.appendChild( child );
740 } else if ( text ) {
741 output.textContent = text;
742 }
743 if ( data ) {
744 Object.keys( data ).forEach( function( dataKey ) {
745 output.setAttribute( 'data-' + dataKey, data[dataKey] );
746 });
747 }
748 return output;
749 }
750
751 function svg({ href, classList } = {}) {
752 const namespace = 'http://www.w3.org/2000/svg';
753 const output = document.createElementNS( namespace, 'svg' );
754 if ( classList ) {
755 output.classList.add( ...classList );
756 }
757
758 if ( href ) {
759 const use = document.createElementNS( namespace, 'use' );
760 use.setAttribute( 'href', href );
761 output.appendChild( use );
762 output.classList.add( 'frmsvg' );
763 }
764 return output;
765 }
766
767 /**
768 * Pop up a success message in the lower right corner.
769 * It then fades out and gets deleted automatically.
770 *
771 * @param {HTMLElement|String} content
772 * @returns {void}
773 */
774 function success( content ) {
775 const container = document.getElementById( 'wpbody' );
776 const notice = div({
777 className: 'frm_updated_message frm-floating-success-message',
778 child: div({
779 className: 'frm-satisfied',
780 child: 'string' === typeof content ? document.createTextNode( content ) : content
781 })
782 });
783 container.appendChild( notice );
784
785 setTimeout(
786 () => jQuery( notice ).fadeOut( () => notice.remove() ),
787 2000
788 );
789 }
790
791 function setAttributes( element, attrs ) {
792 Object.entries( attrs ).forEach(
793 ([ key, value ]) => element.setAttribute( key, value )
794 );
795 }
796
797 function redraw( element, child ) {
798 element.innerHTML = '';
799 element.appendChild( child );
800 }
801
802 const allowedHtml = {
803 b: [],
804 div: [ 'class' ],
805 img: [ 'src', 'alt' ],
806 p: [],
807 span: [ 'class' ],
808 strong: [],
809 svg: [ 'class' ],
810 use: [],
811 a: [ 'href', 'class' ]
812 };
813
814 function cleanNode( node ) {
815 if ( 'undefined' === typeof node.tagName ) {
816 if ( '#text' === node.nodeName ) {
817 return document.createTextNode( node.textContent );
818 }
819 return document.createTextNode( '' );
820 }
821
822 const tagType = node.tagName.toLowerCase();
823
824 if ( 'svg' === tagType ) {
825 const svgArgs = {
826 classList: Array.from( node.classList )
827 };
828 const use = node.querySelector( 'use' );
829 if ( use ) {
830 svgArgs.href = use.getAttribute( 'xlink:href' );
831 if ( ! svgArgs.href ) {
832 svgArgs.href = use.getAttribute( 'href' );
833 }
834 }
835 return svg( svgArgs );
836 }
837
838 const newNode = document.createElement( tagType );
839
840 if ( 'undefined' === typeof allowedHtml[ tagType ]) {
841 // Tag type is not allowed.
842 return document.createTextNode( '' );
843 }
844
845 allowedHtml[ tagType ].forEach(
846 allowedTag => {
847 if ( node.hasAttribute( allowedTag ) ) {
848 newNode.setAttribute( allowedTag, node.getAttribute( allowedTag ) );
849 }
850 }
851 );
852
853 node.childNodes.forEach( child => newNode.appendChild( cleanNode( child ) ) );
854 return newNode;
855 }
856
857 window.frmDom = { tag, div, span, a, img, labelledTextInput, svg, setAttributes, success, modal, ajax, bootstrap, autocomplete, search, util, wysiwyg, cleanNode };
858 }() );
859