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