PluginProbe
Customify / 1.7.1
Customify v1.7.1
2.10.9 1.2.3 1.2.4 1.2.5 1.2.6 1.2.7 1.2.7.1 1.3.0 1.3.1 1.4.0 1.4.1 1.4.2 1.5.0 1.5.1 1.5.2 1.5.3 1.5.4 1.5.5 1.5.6 1.5.7 1.6.0 1.6.0.1 1.6.5 1.7.0 1.7.1 All 77 releases
customify / js / customizer.js

customizer.js in Customify 1.7.1, at js/customizer.js

1,813 lines 59.1 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 (
2 function( $, exports, wp ) {
3 var api = wp.customize;
4 var $window = $( window );
5
6 // when the customizer is ready prepare our fields events
7 wp.customize.bind( 'ready', function() {
8 var timeout = null;
9
10 // add ace editors
11 $( '.customify_ace_editor' ).each( function( key, el ) {
12 var id = $( this ).attr( 'id' ),
13 css_editor = ace.edit( id );
14
15 var editor_type = $( this ).data( 'editor_type' );
16 // init the ace editor
17 css_editor.setTheme( "ace/theme/github" );
18 css_editor.getSession().setMode( "ace/mode/" + editor_type );
19
20 // hide the textarea and enable the ace editor
21 var textarea = $( '#' + id + '_textarea' ).hide();
22 css_editor.getSession().setValue( textarea.val() );
23
24 // each time a change is triggered start a timeout of 1,5s and when is finished refresh the previewer
25 // if the user types faster than this delay then reset it
26 css_editor.getSession().on( 'change', function( e ) {
27 if ( timeout !== null ) {
28 clearTimeout( timeout );
29 timeout = null;
30 } else {
31 timeout = setTimeout( function() {
32 //var state = css_editor.session.getState();
33 textarea.val( css_editor.getSession().getValue() );
34 textarea.trigger( 'change' );
35 }, 1500 );
36 }
37 } );
38 } );
39
40 // simple select2 field
41 $( '.customify_select2' ).select2();
42
43 setTimeout( function() {
44 customifyFontSelect.init( this );
45 }, 333 );
46
47 prepare_typography_field();
48
49 /**
50 * Make the customizer save on CMD/CTRL+S action
51 * This is awesome!!!
52 */
53 $( window ).bind( 'keydown', function( event ) {
54 if ( event.ctrlKey || event.metaKey ) {
55 switch ( String.fromCharCode( event.which ).toLowerCase() ) {
56 case 's':
57 event.preventDefault();
58 api.previewer.save();
59 break;
60 }
61 }
62 } );
63
64 // for each range input add a value preview output
65 $( '.accordion-section-content[id*="' + customify_settings.options_name + '"]' ).each( function() {
66
67 // Initialize range fields logic
68 customifyHandleRangeFields( this );
69 } );
70
71 if ( $( 'button[data-action="reset_customify"]' ).length > 0 ) {
72 // reset_button
73 $( document ).on( 'click', '#customize-control-reset_customify button', function( ev ) {
74 ev.preventDefault();
75
76 var iAgree = confirm( 'Do you really want to reset to defaults all the fields? Watch out, this will reset all your Customify options and will save them!' );
77
78 if ( ! iAgree ) {
79 return;
80 }
81
82 $.each( api.settings.controls, function( key, ctrl ) {
83 var setting_id = key.replace( '_control', '' );
84 var setting = customify_settings.settings[setting_id];
85
86 if ( ! _.isUndefined( setting ) && ! _.isUndefined( setting.default ) ) {
87 api_set_setting_value( setting_id, setting.default );
88 }
89 } );
90
91 api.previewer.save();
92 } );
93
94 // add a reset button for each panel
95 $( '.panel-meta' ).each( function( el, key ) {
96 var container = $( this ).parents( '.control-panel' ),
97 id = container.attr( 'id' );
98
99 if ( typeof id !== 'undefined' ) {
100 var panel_id = id.replace( 'accordion-panel-', '' );
101 $( this ).parent().append( '<button class="reset_panel button" data-panel="' + panel_id + '">Panel\'s defaults</button>' );
102 }
103 } );
104
105 // reset panel
106 $( document ).on( 'click', '.reset_panel', function( e ) {
107 e.preventDefault();
108
109 var panel_id = $( this ).data( 'panel' ),
110 panel = api.panel( panel_id ),
111 sections = panel.sections(),
112 iAgree = confirm( "Do you really want to reset " + panel.params.title + "?" );
113
114 if ( ! iAgree ) {
115 return;
116 }
117 if ( sections.length > 0 ) {
118 $.each( sections, function() {
119 //var settings = this.settings();
120 var controls = this.controls();
121
122 if ( controls.length > 0 ) {
123 $.each( controls, function( key, ctrl ) {
124 var setting_id = ctrl.id.replace( '_control', '' ),
125 setting = customify_settings.settings[setting_id];
126
127 if ( ! _.isUndefined( setting ) && ! _.isUndefined( setting.default ) ) {
128 api_set_setting_value( setting_id, setting.default );
129 }
130 } );
131 }
132 } );
133 }
134 } );
135
136 //add reset section
137 $( '.accordion-section-content' ).each( function( el, key ) {
138 var section_id = $( this ).attr( 'id' );
139
140 if ( (
141 (
142 ! _.isUndefined( section_id )
143 ) ? section_id.indexOf( customify_settings.options_name ) : - 1
144 ) === - 1 ) {
145 return;
146 }
147
148 if ( ! _.isUndefined( section_id ) && section_id.indexOf( 'sub-accordion-section-' ) > - 1 ) {
149 var id = section_id.replace( 'sub-accordion-section-', '' );
150 $( this ).append( '<button class="reset_section button" data-section="' + id + '">Reset All Options for This Section</button>' );
151 }
152 } );
153
154 // reset section event
155 $( document ).on( 'click', '.reset_section', function( e ) {
156 e.preventDefault();
157
158 var section_id = $( this ).data( 'section' ),
159 section = api.section( section_id ),
160 controls = section.controls();
161
162 var iAgree = confirm( "Do you really want to reset " + section.params.title + "?" );
163
164 if ( ! iAgree ) {
165 return;
166 }
167
168 if ( controls.length > 0 ) {
169 $.each( controls, function( key, ctrl ) {
170 var setting_id = ctrl.id.replace( '_control', '' ),
171 setting = customify_settings.settings[setting_id];
172
173 if ( ! _.isUndefined( setting ) && ! _.isUndefined( setting.default ) ) {
174 api_set_setting_value( setting_id, setting.default );
175 }
176 } );
177 }
178 } );
179 }
180
181 $( document ).on( 'change keyup', '.customize-control-range input.range-value', function() {
182 var range = $( this ).siblings( 'input[type="range"]' );
183 range.val( $( this ).val() );
184 range.trigger( 'change' );
185 } );
186
187 $( document ).on( 'change', '.customify_typography_font_subsets', function( ev ) {
188
189 var $input = $( this ).parents( '.options' ).siblings( '.customify_typography' ).children( '.customify_typography_values' ),
190 current_val = $input.val();
191
192 current_val = JSON.parse( decodeURIComponent( current_val ) );
193
194 //maybe the selected option holds a JSON in its value
195 current_val.selected_subsets = maybeJsonParse( $( this ).val() );
196
197 $input.val( encodeURIComponent( JSON.stringify( current_val ) ) );
198
199 $input.trigger( 'change' );
200 } );
201
202 $( document ).on( 'change', '.customify_typography_font_weight', function( ev ) {
203
204 var $input = $( this ).parents( '.options' ).siblings( '.customify_typography' ).children( '.customify_typography_values' ),
205 current_val = $input.val();
206
207 current_val = maybeJsonParse( current_val );
208 // @todo currently the font weight selector works for one value only
209 // maybe make this a multiselect
210
211 //maybe the selected option holds a JSON in its value
212 current_val.selected_variants = {0: maybeJsonParse( $( this ).val() )};
213
214 $input.val( encodeURIComponent( JSON.stringify( current_val ) ) );
215 $input.trigger( 'change' );
216 } );
217
218 $( 'body' ).on( 'customify:preset-change', function( e ) {
219 const data = $( e.target ).data( 'options' );
220
221 if ( ! _.isUndefined( data ) ) {
222 $.each( data, function( setting_id, value ) {
223 api_set_setting_value( setting_id, value );
224 } );
225 }
226 } );
227
228 $( document ).on( 'change', '.customify_preset.select', function() {
229 const $source = $( this );
230 const $target = $source.children( '[value="' + $source.val() + '"]' );
231 $target.trigger( 'customify:preset-change' );
232 } );
233
234 $( document ).on( 'click', '.customify_preset.radio input, .customify_preset.radio_buttons input, .awesome_presets input', function() {
235 $( this ).trigger( 'customify:preset-change' );
236 } );
237
238 // bind our event on click
239 $( document ).on( 'click', '.customify_import_demo_data_button', function( event ) {
240 let key = $( this ).data( 'key' );
241 let import_queue = new Queue( api );
242 let steps = [];
243
244 if ( ! _.isUndefined( customify_settings.settings[key].imports ) ) {
245
246 $.each( customify_settings.settings[key].imports, function( i, import_setts, k ) {
247 if ( _.isUndefined( import_setts.steps ) ) {
248 steps.push( {id: i, type: import_setts.type} );
249 } else {
250 var count = import_setts.steps;
251
252 while ( count >= 1 ) {
253 steps.push( {id: i, type: import_setts.type, count: count} );
254 count = count - 1;
255 }
256 }
257 } );
258 }
259
260 import_queue.add_steps( 'import_demo_data_action_id', steps );
261 return false;
262 } );
263
264 customifyBackgroundJsControl.init();
265
266 // sometimes a php save may be needed
267 if ( getUrlVars( 'save_customizer_once' ) ) {
268 api.previewer.save();
269 }
270
271 setTimeout( function() {
272 customifyFoldingFields();
273 }, 1000 );
274
275
276 // Handle the section tabs (ex: Layout | Fonts | Colors)
277 (
278 function() {
279 var $navs = $( '.js-section-navigation' );
280
281 $navs.each( function() {
282 var $nav = $( this );
283 var $title = $nav.parents( '.accordion-section-content' ).find( '.customize-section-title' );
284
285 $nav.closest( '.customize-control' ).addClass( 'screen-reader-text' );
286 $title.append( $nav ).parent().addClass( 'has-nav' );
287 } );
288
289 $( '.js-section-navigation a' ).on( 'click', function( e ) {
290 e.preventDefault();
291
292 var $sidebar = $( this ).parents( '.customize-pane-child' );
293 var $parent = $( this ).parents( '.accordion-section-content' );
294 var href = $.attr( this, 'href' );
295
296 if ( href != '#' ) {
297 $sidebar.animate( {
298 scrollTop: $( $.attr( this, 'href' ) ).position().top - $parent.find( '.customize-section-title' ).outerHeight()
299 }, 500 );
300 }
301 } );
302 }
303 )();
304
305 (
306 function() {
307 // Close a font field when clicking on another field
308 $( '.customify_font_tooltip' ).on( 'click', function() {
309 if ( $( this ).prop( 'checked' ) === true ) {
310 $( '.customify_font_tooltip' ).prop( 'checked', false );
311 $( this ).prop( 'checked', true );
312 }
313 } );
314 }
315 )();
316
317 // Handle the Style Manager user feedback logic.
318 var $styleManagerUserFeedbackModal = $('#style-manager-user-feedback-modal');
319 if ( $styleManagerUserFeedbackModal.length ) {
320 var $styleManagerUserFeedbackForm = $styleManagerUserFeedbackModal.find('form'),
321 $styleManagerUserFeedbackCloseBtn = $styleManagerUserFeedbackModal.find('.close'),
322 $styleManagerUserFeedbackFirstStep = $styleManagerUserFeedbackModal.find('.first-step'),
323 $styleManagerUserFeedbackSecondStep = $styleManagerUserFeedbackModal.find('.second-step'),
324 $styleManagerUserFeedbackThanksStep = $styleManagerUserFeedbackModal.find('.thanks-step'),
325 $styleManagerUserFeedbackErrorStep = $styleManagerUserFeedbackModal.find('.error-step'),
326 styleManagerUserFeedbackModalShown = false,
327 styleManagerColorPaletteChanged = false;
328
329 // Handle when to open the modal.
330 api.bind('saved', function () {
331 // We will only show the modal once per Customizer session.
332 if (!styleManagerUserFeedbackModalShown && styleManagerColorPaletteChanged) {
333 $('body').addClass('modal-open');
334 styleManagerUserFeedbackModalShown = true;
335 }
336 });
337
338 // Handle the color palette changed info update.
339 const colorPaletteSetting = api( 'sm_color_palette' );
340 if ( !_.isUndefined(colorPaletteSetting) ) {
341 colorPaletteSetting.bind( function( new_value, old_value ) {
342 if ( new_value != old_value ) {
343 styleManagerColorPaletteChanged = true;
344 }
345 } )
346 }
347 const colorPaletteVariationSetting = api( 'sm_color_palette_variation' );
348 if ( !_.isUndefined(colorPaletteVariationSetting) ) {
349 colorPaletteVariationSetting.bind( function( new_value, old_value ) {
350 if ( new_value != old_value ) {
351 styleManagerColorPaletteChanged = true;
352 }
353 } )
354 }
355
356 // Handle the modal submit.
357 $styleManagerUserFeedbackForm.on('submit', function (event) {
358 event.preventDefault();
359
360 let $form = $(event.target);
361
362 let data = {
363 action: 'customify_style_manager_user_feedback',
364 nonce: customify_settings.style_manager_user_feedback_nonce,
365 type: $form.find('input[name=type]').val(),
366 rating: $form.find('input[name=rating]:checked').val(),
367 message: $form.find('textarea[name=message]').val()
368 };
369
370 $.post(
371 customify_settings.ajax_url,
372 data,
373 function (response) {
374 if (true === response.success) {
375 $styleManagerUserFeedbackFirstStep.hide();
376 $styleManagerUserFeedbackSecondStep.hide();
377 $styleManagerUserFeedbackThanksStep.show();
378 $styleManagerUserFeedbackErrorStep.hide();
379 } else {
380 $styleManagerUserFeedbackFirstStep.hide();
381 $styleManagerUserFeedbackSecondStep.hide();
382 $styleManagerUserFeedbackThanksStep.hide();
383 $styleManagerUserFeedbackErrorStep.show();
384 }
385 }
386 );
387 });
388
389 $styleManagerUserFeedbackForm.find('input[name=rating]').on('change', function (event) {
390 // Leave everything in working order
391 setTimeout(function () {
392 $styleManagerUserFeedbackSecondStep.show();
393 }, 300);
394
395 let rating = $styleManagerUserFeedbackForm.find('input[name=rating]:checked').val();
396
397 $styleManagerUserFeedbackForm.find('.rating-placeholder').text(rating);
398 });
399
400 $styleManagerUserFeedbackCloseBtn.on('click', function (event) {
401 event.preventDefault();
402
403 $('body').removeClass('modal-open');
404
405 // Leave everything in working order
406 setTimeout(function () {
407 $styleManagerUserFeedbackFirstStep.show();
408 $styleManagerUserFeedbackSecondStep.hide();
409 $styleManagerUserFeedbackThanksStep.hide();
410 $styleManagerUserFeedbackErrorStep.hide();
411 }, 300);
412 });
413 }
414 } );
415
416 const customifyHandleRangeFields = function( el ) {
417
418 // For each range input add a number field (for preview mainly - but it can also be used for input)
419 $( el ).find( 'input[type="range"]' ).each( function() {
420 if ( ! $( this ).siblings( '.range-value' ).length ) {
421 var $clone = $( this ).clone();
422
423 $clone
424 .attr( 'type', 'number' )
425 .attr( 'class', 'range-value' )
426 .removeAttr( 'data-field' );
427
428 $( this ).after( $clone );
429 }
430
431 // Update the number field when changing the range
432 $( this ).on( 'change', function() {
433 $( this ).siblings( '.range-value' ).val( $( this ).val() );
434 } );
435
436 // And the other way around, update the range field when changing the number
437 $( $clone ).on( 'change', function() {
438 $( this ).siblings( 'input[type="range"]' ).val( $( this ).val() );
439 } );
440 } );
441 };
442
443 /**
444 * This function will search for all the interdependend fields and make a bound between them.
445 * So whenever a target is changed, it will take actions to the dependent fields.
446 * @TODO this is still written in a barbaric way, refactor when needed
447 */
448 var customifyFoldingFields = function() {
449
450 if ( _.isUndefined( customify_settings ) || _.isUndefined( customify_settings.settings ) ) {
451 return; // bail
452 }
453
454 /**
455 * Let's iterate through all the customify settings and gather all the fields that have a "show_if"
456 * property set.
457 *
458 * At the end `targets` will hold a list of [ target : [field, field,...], ... ]
459 * so when a target is changed we will change all the fields.
460 */
461 var targets = {};
462
463 $.fn.reactor.defaults.compliant = function() {
464 $( this ).slideDown();
465 // $(this).animate({opacity: 1});
466 $( this ).find( ':disabled' ).attr( {disabled: false} );
467 };
468
469 $.fn.reactor.defaults.uncompliant = function() {
470 $( this ).slideUp();
471 // $(this).animate({opacity: 0.25});
472 $( this ).find( ':enabled' ).attr( {disabled: true} );
473 };
474
475 var IS = $.extend( {}, $.fn.reactor.helpers );
476
477 var bind_folding_events = function( parent_id, field, relation ) {
478
479 var key = null;
480
481 if ( _.isString( field ) ) {
482 key = field;
483 } else if ( ! _.isUndefined( field.id ) ) {
484 key = field.id;
485 } else if ( isString( field[0] ) ) {
486 key = field[0];
487 } else {
488 return; // no key, no fun
489 }
490
491 var value = 1, // by default we use 1 the most used value for checkboxes or inputs
492 compare = '==', // ... ye
493 action = "show",
494 between = [0, 1]; // can only be `show` or `hide`
495
496 var target_key = customify_settings.options_name + '[' + key + ']';
497
498 var target_type = customify_settings.settings[target_key].type;
499
500 // we support the usual syntax like a config array like `array( 'id' => $id, 'value' => $value, 'compare' => $compare )`
501 // but we also support a non-associative array like `array( $id, $value, $compare )`
502 if ( ! _.isUndefined( field.value ) ) {
503 value = field.value;
504 } else if ( ! _.isUndefined( field[1] ) && ! _.isString( field[1] ) ) {
505 value = field[1];
506 }
507
508 if ( ! _.isUndefined( field.compare ) ) {
509 compare = field.compare;
510 } else if ( ! _.isUndefined( field[2] ) ) {
511 compare = field[2];
512 }
513
514 if ( ! _.isUndefined( field.action ) ) {
515 action = field.action;
516 } else if ( ! _.isUndefined( field[3] ) ) {
517 action = field[3];
518 }
519
520 // a field can also overwrite the parent relation
521 if ( ! _.isUndefined( field.relation ) ) {
522 action = field.relation;
523 } else if ( ! _.isUndefined( field[4] ) ) {
524 action = field[4];
525 }
526
527 if ( ! _.isUndefined( field.between ) ) {
528 between = field.between;
529 }
530
531 /**
532 * Now for each target we have, we will bind a change event to hide or show the dependent fields
533 */
534 var target_selector = '[data-customize-setting-link="' + customify_settings.options_name + '[' + key + ']"]';
535
536 switch ( target_type ) {
537 case 'checkbox':
538 $( parent_id ).reactIf( target_selector, function() {
539 return $( this ).is( ':checked' ) == value;
540 } );
541 break;
542
543 case 'radio':
544 case 'radio_image':
545
546 // in case of an array of values we use the ( val in array) condition
547 if ( _.isObject( value ) ) {
548 $( parent_id ).reactIf( target_selector, function() {
549 return (
550 value.indexOf( $( target_selector + ':checked' ).val() ) !== - 1
551 );
552 } );
553 } else { // in any other case we use a simple == comparison
554 $( parent_id ).reactIf( target_selector, function() {
555 return $( target_selector + ':checked' ).val() == value;
556 } );
557 }
558 break;
559
560 case 'range':
561 var x = IS.Between( between[0], between[1] );
562
563 $( parent_id ).reactIf( target_selector, x );
564 break;
565
566 default:
567 // in case of an array of values we use the ( val in array) condition
568 if ( _.isObject( value ) ) {
569 $( parent_id ).reactIf( target_selector, function() {
570 return (
571 value.indexOf( $( target_selector ).val() ) !== - 1
572 );
573 } );
574 } else { // in any other case we use a simple == comparison
575 $( parent_id ).reactIf( target_selector, function() {
576 return $( target_selector ).val() == value;
577 } );
578 }
579 break;
580 }
581
582 $( target_selector ).trigger( 'change' );
583 $( '.reactor' ).trigger( 'change.reactor' ); // triggers all events on load
584 };
585
586 $.each( customify_settings.settings, function( id, field ) {
587 /**
588 * Here we have the id of the fields. but we know for sure that we just need his parent selector
589 * So we just create it
590 */
591 var parent_id = id.replace( '[', '-' );
592 parent_id = parent_id.replace( ']', '' );
593 parent_id = '#customize-control-' + parent_id + '_control';
594
595 // get only the fields that have a 'show_if' property
596 if ( field.hasOwnProperty( 'show_if' ) ) {
597 var relation = 'AND';
598
599 if ( ! _.isUndefined( field.show_if.relation ) ) {
600 relation = field.show_if.relation;
601 // remove the relation property, we need the config to be array based only
602 delete field.show_if.relation;
603 }
604
605 /**
606 * The 'show_if' can be a simple array with one target like: [ id, value, comparison, action ]
607 * Or it could be an array of multiple targets and we need to process both cases
608 */
609
610 if ( ! _.isUndefined( field.show_if.id ) ) {
611 bind_folding_events( parent_id, field.show_if, relation );
612 } else if ( _.isObject( field.show_if ) ) {
613 $.each( field.show_if, function( i, j ) {
614 bind_folding_events( parent_id, j, relation );
615 } );
616 }
617 }
618 } );
619 };
620
621 var get_typography_font_family = function( $el ) {
622
623 var font_family_value = $el.val();
624 // first time this will not be a json so catch that error
625 try {
626 font_family_value = JSON.parse( font_family_value );
627 } catch ( e ) {
628 return {font_family: font_family_value};
629 }
630
631 if ( ! _.isUndefined( font_family_value.font_family ) ) {
632 return font_family_value.font_family;
633 }
634
635 return false;
636 };
637
638 // get each typography field and bind events
639 // @todo Are we still using the typography field since we have the font field?
640 var prepare_typography_field = function() {
641
642 var $typos = $( '.customify_typography_font_family' );
643
644 $typos.each( function() {
645 var font_family_select = this,
646 $input = $( font_family_select ).siblings( '.customify_typography_values' );
647 // on change
648 $( font_family_select ).on( 'change', function() {
649 update_siblings_selects( font_family_select );
650 $input.trigger( 'change' );
651 } );
652 update_siblings_selects( font_family_select );
653 } );
654 };
655
656 var api_set_setting_value = function( setting_id, value ) {
657 let setting = api( setting_id ),
658 field = $( '[data-customize-setting-link="' + setting_id + '"]' ),
659 field_class = $( field ).parent().attr( 'class' );
660
661 // Legacy field type
662 if ( ! _.isUndefined( field_class ) && field_class === 'customify_typography' ) {
663
664 let family_select = field.siblings( 'select' );
665
666 if ( _.isString( value ) ) {
667 let this_option = family_select.find( 'option[value="' + value + '"]' );
668 $( this_option[0] ).attr( 'selected', 'selected' );
669 update_siblings_selects( family_select );
670 } else if ( _.isObject( value ) ) {
671 let this_family_option = family_select.find( 'option[value="' + value['font_family'] + '"]' );
672
673 $( this_family_option[0] ).attr( 'selected', 'selected' );
674
675 update_siblings_selects( this_family_option );
676
677 setTimeout( function() {
678 let weight_select = field.parent().siblings( '.options' ).find( '.customify_typography_font_weight' ),
679 this_weight_option = weight_select.find( 'option[value="' + value['selected_variants'] + '"]' );
680
681 $( this_weight_option[0] ).attr( 'selected', 'selected' );
682
683 update_siblings_selects( this_family_option );
684
685 weight_select.trigger( 'change' );
686 }, 300 );
687 }
688
689 family_select.trigger( 'change' );
690
691 } else if ( ! _.isUndefined( field_class ) && field_class === 'font-options__wrapper' ) {
692
693 // if the value is a simple string it must be the font family
694 if ( _.isString( value ) ) {
695 let option = field.parent().find( 'option[value="' + value + '"]' );
696
697 option.attr( 'selected', 'selected' );
698 // option.parents('select').trigger('change');
699 } else if ( _.isObject( value ) ) {
700 // Find the options list wrapper
701 let optionsList = field.parent().children( '.font-options__options-list' );
702
703 if ( optionsList.length ) {
704 // We will process each font property and update it
705 _.each( value, function( val, key ) {
706 // We need to map the keys to the data attributes we are using - I know :(
707 let mappedKey = key;
708 switch ( key ) {
709 case 'font-family':
710 mappedKey = 'font_family';
711 break;
712 case 'font-size':
713 mappedKey = 'font_size';
714 break;
715 case 'font-weight':
716 mappedKey = 'selected_variants';
717 break;
718 case 'letter-spacing':
719 mappedKey = 'letter_spacing';
720 break;
721 case 'text-transform':
722 mappedKey = 'text_transform';
723 break;
724 default:
725 break;
726 }
727 let subField = optionsList.find( '[data-field="' + mappedKey + '"]' );
728 if ( subField.length ) {
729 subField.val( val );
730 subField.trigger( 'change' );
731 }
732 } );
733 }
734 }
735
736 } else {
737 setting.set( value );
738 }
739 };
740
741 var update_siblings_selects = function( font_select ) {
742 var selected_font = $( font_select ).val(),
743 $input = $( font_select ).siblings( '.customify_typography_values' ),
744 current_val = $input.attr( 'value' );
745
746 if ( current_val === '[object Object]' ) {
747 current_val = $input.data( 'default' );
748 } else if ( _.isString( current_val ) && ! isJsonString( current_val ) && current_val.substr( 0, 1 ) == '[' ) {
749 // a rare case when the value isn't a json but is a representative string like [family,weight]
750 current_val = current_val.split( ',' );
751 var new_current_value = {};
752 if ( ! _.isUndefined( current_val[0] ) ) {
753 new_current_value['font_family'] = current_val[0];
754 }
755
756 if ( ! _.isUndefined( current_val[1] ) ) {
757 new_current_value['selected_variants'] = current_val[1];
758 }
759
760 current_val = JSON.stringify( new_current_value );
761 }
762
763 var $font_weight = $( font_select ).parent().siblings( 'ul.options' ).find( '.customify_typography_font_weight' ),
764 $font_subsets = $( font_select ).parent().siblings( 'ul.options' ).find( '.customify_typography_font_subsets' );
765
766 try {
767 current_val = JSON.parse( decodeURIComponent( current_val ) );
768 } catch ( e ) {
769
770 // in case of an error, force the rebuild of the json
771 if ( _.isUndefined( $( font_select ).data( 'bound_once' ) ) ) {
772
773 $( font_select ).data( 'bound_once', true );
774
775 $( font_select ).change();
776 $font_weight.change();
777 $font_subsets.change();
778 }
779 }
780
781 // first try to get the font from sure sources, not from the recommended list.
782 var option_data = $( font_select ).find( ':not(optgroup[label=Recommended]) option[value="' + selected_font + '"]' );
783 // however, if there isn't an option found, get what you can
784 if ( option_data.length < 1 ) {
785 option_data = $( font_select ).find( 'option[value="' + selected_font + '"]' );
786 }
787
788 if ( option_data.length > 0 ) {
789
790 var font_type = option_data.data( 'type' ),
791 value_to_add = {'type': font_type, 'font_family': selected_font},
792 variants = null,
793 subsets = null;
794
795 if ( font_type == 'std' ) {
796 variants = {
797 0: '100',
798 1: '200',
799 3: '300',
800 4: '400',
801 5: '500',
802 6: '600',
803 7: '700',
804 8: '800',
805 9: '900'
806 };
807 if ( ! _.isUndefined( $( option_data[0] ).data( 'variants' ) ) ) {
808 //maybe the variants are a JSON
809 variants = maybeJsonParse( $( option_data[0] ).data( 'variants' ) );
810 }
811 } else {
812 //maybe the variants are a JSON
813 variants = maybeJsonParse( $( option_data[0] ).data( 'variants' ) );
814
815 //maybe the subsets are a JSON
816 subsets = maybeJsonParse( $( option_data[0] ).data( 'subsets' ) );
817 }
818
819 // make the variants selector
820 if ( ! _.isUndefined( variants ) && ! _.isNull( variants ) && ! _.isEmpty( variants ) ) {
821
822 value_to_add['variants'] = variants;
823 // when a font is selected force the first weight to load
824 value_to_add['selected_variants'] = {0: variants[0]};
825
826 var variants_options = '',
827 count_weights = 0;
828
829 if ( _.isArray( variants ) || _.isObject( variants ) ) {
830 // Take each variant and produce the option markup
831 $.each( variants, function( key, el ) {
832 var is_selected = '';
833 if ( _.isObject( current_val.selected_variants ) && inObject( el, current_val.selected_variants ) ) {
834 is_selected = ' selected="selected"';
835 } else if ( _.isString( current_val.selected_variants ) && el === current_val.selected_variants ) {
836 is_selected = ' selected="selected"';
837 }
838
839 // initialize
840 var variant_option_value = el,
841 variant_option_display = el;
842
843 // If we are dealing with a object variant then it means things get tricky (probably it's our fault but bear with us)
844 // This probably comes from our Fonto plugin - a font with individually named variants - hence each has its own font-family
845 if ( _.isObject( el ) ) {
846 //put the entire object in the variation value - we will need it when outputting the custom CSS
847 variant_option_value = encodeURIComponent( JSON.stringify( el ) );
848 variant_option_display = '';
849
850 //if we have weight and style then "compose" them into something standard
851 if ( ! _.isUndefined( el['font-weight'] ) ) {
852 variant_option_display += el['font-weight'];
853 }
854
855 if ( _.isString( el['font-style'] ) && $.inArray( el['font-style'].toLowerCase(), [
856 "normal",
857 "regular"
858 ] ) < 0 ) { //this comparison means it hasn't been found
859 variant_option_display += el['font-style'];
860 }
861 }
862
863 variants_options += '<option value="' + variant_option_value + '"' + is_selected + '>' + variant_option_display + '</option>';
864 count_weights ++;
865 } );
866 }
867
868 if ( ! _.isUndefined( $font_weight ) ) {
869 $font_weight.html( variants_options );
870 // if there is no weight or just 1 we hide the weight select ... cuz is useless
871 if ( $( font_select ).data( 'load_all_weights' ) === true || count_weights <= 1 ) {
872 $font_weight.parent().css( 'display', 'none' );
873 } else {
874 $font_weight.parent().css( 'display', 'inline-block' );
875 }
876 }
877 } else if ( ! _.isUndefined( $font_weight ) ) {
878 $font_weight.parent().css( 'display', 'none' );
879 }
880
881 // make the subsets selector
882 if ( ! _.isUndefined( subsets ) && ! _.isNull( subsets ) && ! _.isEmpty( subsets ) ) {
883
884 value_to_add['subsets'] = subsets;
885 // when a font is selected force the first subset to load
886 value_to_add['selected_subsets'] = {0: subsets[0]};
887 var subsets_options = '',
888 count_subsets = 0;
889 $.each( subsets, function( key, el ) {
890 var is_selected = '';
891 if ( _.isObject( current_val.selected_subsets ) && inObject( el, current_val.selected_subsets ) ) {
892 is_selected = ' selected="selected"';
893 }
894
895 subsets_options += '<option value="' + el + '"' + is_selected + '>' + el + '</option>';
896 count_subsets ++;
897 } );
898
899 if ( ! _.isUndefined( $font_subsets ) ) {
900 $font_subsets.html( subsets_options );
901
902 // if there is no subset or just 1 we hide the subsets select ... cuz is useless
903 if ( count_subsets <= 1 ) {
904 $font_subsets.parent().css( 'display', 'none' );
905 } else {
906 $font_subsets.parent().css( 'display', 'inline-block' );
907 }
908 }
909 } else if ( ! _.isUndefined( $font_subsets ) ) {
910 $font_subsets.parent().css( 'display', 'none' );
911 }
912
913 $input.val( encodeURIComponent( JSON.stringify( value_to_add ) ) );
914 }
915 };
916
917 /** Modules **/
918
919 var customifyBackgroundJsControl = (
920 function() {
921 "use strict";
922
923 function init() {
924 // Remove the image button
925 $( '.customize-control-custom_background .remove-image, .customize-control-custom_background .remove-file' ).unbind( 'click' ).on( 'click', function( e ) {
926 removeImage( $( this ).parents( '.customize-control-custom_background:first' ) );
927 preview( $( this ) );
928 return false;
929 } );
930
931 // Upload media button
932 $( '.customize-control-custom_background .background_upload_button' ).unbind().on( 'click', function( event ) {
933 addImage( event, $( this ).parents( '.customize-control-custom_background:first' ) );
934 } );
935
936 $( '.customify_background_select' ).on( 'change', function() {
937 preview( $( this ) );
938 } );
939 }
940
941 // Add a file via the wp.media function
942 function addImage( event, selector ) {
943
944 event.preventDefault();
945
946 var frame;
947 var jQueryel = jQuery( this );
948
949 // If the media frame already exists, reopen it.
950 if ( frame ) {
951 frame.open();
952 return;
953 }
954
955 // Create the media frame.
956 frame = wp.media( {
957 multiple: false,
958 library: {
959 //type: 'image' //Only allow images
960 },
961 // Set the title of the modal.
962 title: jQueryel.data( 'choose' ),
963
964 // Customize the submit button.
965 button: {
966 // Set the text of the button.
967 text: jQueryel.data( 'update' )
968 // Tell the button not to close the modal, since we're
969 // going to refresh the page when the image is selected.
970 }
971 } );
972
973 // When an image is selected, run a callback.
974 frame.on( 'select', function() {
975 // Grab the selected attachment.
976 var attachment = frame.state().get( 'selection' ).first();
977 frame.close();
978
979 if ( attachment.attributes.type !== "image" ) {
980 return;
981 }
982
983 selector.find( '.upload' ).attr( 'value', attachment.attributes.url );
984 selector.find( '.upload-id' ).attr( 'value', attachment.attributes.id );
985 selector.find( '.upload-height' ).attr( 'value', attachment.attributes.height );
986 selector.find( '.upload-width' ).attr( 'value', attachment.attributes.width );
987
988 var thumbSrc = attachment.attributes.url;
989 if ( ! _.isUndefined( attachment.attributes.sizes ) && ! _.isUndefined( attachment.attributes.sizes.thumbnail ) ) {
990 thumbSrc = attachment.attributes.sizes.thumbnail.url;
991 } else if ( ! _.isUndefined( attachment.attributes.sizes ) ) {
992 var height = attachment.attributes.height;
993 for ( var key in attachment.attributes.sizes ) {
994 var object = attachment.attributes.sizes[key];
995 if ( object.height < height ) {
996 height = object.height;
997 thumbSrc = object.url;
998 }
999 }
1000 } else {
1001 thumbSrc = attachment.attributes.icon;
1002 }
1003
1004 selector.find( '.customify_background_input.background-image' ).val( attachment.attributes.url );
1005
1006 if ( ! selector.find( '.upload' ).hasClass( 'noPreview' ) ) {
1007 selector.find( '.preview_screenshot' ).empty().hide().append( '<img class="preview_image" src="' + thumbSrc + '">' ).slideDown( 'fast' );
1008 }
1009 //selector.find('.media_upload_button').unbind();
1010 selector.find( '.remove-image' ).removeClass( 'hide' );//show "Remove" button
1011 selector.find( '.customify_background_select' ).removeClass( 'hide' );//show "Remove" button
1012
1013 preview( selector );
1014 } );
1015
1016 // Finally, open the modal.
1017 frame.open();
1018 }
1019
1020 // Update the background preview
1021 function preview( selector ) {
1022
1023 var $parent = selector.parents( '.customize-control-custom_background:first' );
1024
1025 if ( selector.hasClass( 'customize-control-custom_background' ) ) {
1026 $parent = selector;
1027 }
1028
1029 if ( $parent.length > 0 ) {
1030 $parent = $( $parent[0] );
1031 } else {
1032 return;
1033 }
1034
1035 var image_holder = $parent.find( '.background-preview' );
1036
1037 if ( ! image_holder ) { // No preview present
1038 return;
1039 }
1040
1041 var the_id = $parent.find( '.button.background_upload_button' ).data( 'setting_id' ),
1042 this_setting = api.instance( the_id );
1043
1044 var background_data = {};
1045
1046 $parent.find( '.customify_background_select, .customify_background_input' ).each( function() {
1047 var data = $( this ).serializeArray();
1048
1049 data = data[0];
1050 if ( data && data.name.indexOf( '[background-' ) != - 1 ) {
1051
1052 background_data[$( this ).data( 'select_name' )] = data.value;
1053
1054 //default_default[data.name] = data.value;
1055 //if (data.name == "background-image") {
1056 // css += data.name + ':url("' + data.value + '");';
1057 //} else {
1058 // css += data.name + ':' + data.value + ';';
1059 //}
1060 }
1061 } );
1062
1063 api.instance( the_id ).set( background_data );
1064 //// Notify the customizer api about this change
1065 api.trigger( 'change' );
1066 api.previewer.refresh();
1067
1068 //image_holder.attr('style', css).fadeIn();
1069 }
1070
1071 // Update the background preview
1072 function removeImage( parent ) {
1073 var selector = parent.find( '.upload_button_div' );
1074 // This shouldn't have been run...
1075 if ( ! selector.find( '.remove-image' ).addClass( 'hide' ) ) {
1076 return;
1077 }
1078
1079 selector.find( '.remove-image' ).addClass( 'hide' );//hide "Remove" button
1080 parent.find( '.customify_background_select' ).addClass( 'hide' );
1081
1082 selector.find( '.upload' ).val( '' );
1083 selector.find( '.upload-id' ).val( '' );
1084 selector.find( '.upload-height' ).val( '' );
1085 selector.find( '.upload-width' ).val( '' );
1086 parent.find( '.customify_background_input.background-image' ).val( '' );
1087
1088 var customizer_id = selector.find( '.background_upload_button' ).data( 'setting_id' ),
1089 this_setting = api.control( customizer_id + '_control' ),
1090 current_vals = this_setting.setting(),
1091 screenshot = parent.find( '.preview_screenshot' ),
1092 to_array = $.map( current_vals, function( value, index ) {
1093 return [value];
1094 } );
1095
1096 // Hide the screenshot
1097 screenshot.slideUp();
1098 selector.find( '.remove-file' ).unbind();
1099 to_array['background-image'] = '';
1100 this_setting.setting( to_array );
1101 }
1102
1103 return {
1104 init: init
1105 }
1106 }
1107 )( jQuery );
1108
1109 // This is for the Font control
1110 var customifyFontSelect = (
1111 function() {
1112 const
1113 wrapperSelector = '.font-options__wrapper',
1114 valueHolderSelector = '.customify_font_values',
1115 fontFamilySelector = '.customify_font_family',
1116 fontWeightSelector = '.customify_font_weight',
1117 fontSubsetsSelector = '.customify_font_subsets',
1118 selectPlaceholder = "Select a font family",
1119 weightPlaceholder = "Select a font weight",
1120 subsetPlaceholder = "Extra Subsets";
1121
1122 // We will use this to remember that we are self-updating the field from the subfields.
1123 // We will save this info for each setting ID.
1124 var updatingValue = {},
1125 loadingValue = {};
1126
1127 function init( wpapi ) {
1128 let $fontFamilyFields = $( fontFamilySelector );
1129
1130 // Initialize the select2 field for the font family
1131 $fontFamilyFields.select2( {
1132 placeholder: selectPlaceholder
1133 } ).on( 'change', function( e ) {
1134 let new_option = $( e.target ).find( 'option:selected' ),
1135 wrapper = $( e.target ).closest( wrapperSelector );
1136
1137 // Update the weight subfield with the new options given by the selected font family.
1138 update_weight_field( new_option, wrapper );
1139
1140 // Update the subset subfield with the new options given by the selected font family.
1141 update_subset_field( new_option, wrapper );
1142
1143 // Serialize subfield values and refresh the fonts in the preview window.
1144 update_font_value( wrapper );
1145 } );
1146
1147 // Initialize the select2 field for the font weight
1148 $( fontWeightSelector ).each( function( i, el ) {
1149
1150 let select2_args = {
1151 placeholder: weightPlaceholder
1152 };
1153
1154 // all this fuss is for the case when the font doesn't come with variants from PHP, like a theme_font
1155 if ( this.options.length === 0 ) {
1156 var wrapper = $( el ).closest( wrapperSelector ),
1157 font = wrapper.find( fontFamilySelector ),
1158 option = font[0].options[font[0].selectedIndex],
1159 variants = maybeJsonParse( $( option ).data( 'variants' ) ),
1160 data = [],
1161 selecter_variants = $( el ).data( 'default' ) || null;
1162
1163 if ( typeof variants === "undefined" ) {
1164 $( this ).hide();
1165 return;
1166 }
1167
1168 $.each( variants, function( index, weight ) {
1169 let this_value = {
1170 id: weight,
1171 text: weight
1172 };
1173
1174 if ( selecter_variants !== null && weight == selecter_variants ) {
1175 this_value.selected = true;
1176 }
1177
1178 data.push( this_value );
1179 } );
1180
1181 if ( data !== [] ) {
1182 select2_args.data = data;
1183 }
1184 }
1185
1186 $( this ).select2(
1187 select2_args
1188 ).on( 'change', function( e ) {
1189 let wrapper = $( e.target ).closest( wrapperSelector );
1190
1191 // Serialize subfield values and refresh the fonts in the preview window.
1192 update_font_value( wrapper );
1193 } );
1194 } );
1195
1196 // Initialize the select2 field for the font subsets
1197 $( fontSubsetsSelector )
1198 .select2( {
1199 placeholder: subsetPlaceholder
1200 } )
1201 .on( 'change', function( e ) {
1202 let wrapper = $( e.target ).closest( wrapperSelector );
1203
1204 // Serialize subfield values and refresh the fonts in the preview window.
1205 update_font_value( wrapper );
1206 } );
1207
1208 let rangers = $fontFamilyFields.parents( wrapperSelector ).find( 'input[type=range]' ),
1209 selects = $fontFamilyFields.parents( wrapperSelector ).find( 'select' ).not( "select[class*=' select2'],select[class^='select2']" );
1210
1211 // Initialize the all the regular selects in the font controls
1212 if ( selects.length > 0 ) {
1213 selects.on( 'change', function( e ) {
1214 let wrapper = $( e.target ).closest( wrapperSelector );
1215
1216 // Serialize subfield values and refresh the fonts in the preview window.
1217 update_font_value( wrapper );
1218 } );
1219 }
1220
1221 // Initialize the all the range fields in the font controls
1222 if ( rangers.length > 0 ) {
1223 rangers.on( 'change', function( e ) {
1224 let wrapper = $( e.target ).closest( wrapperSelector );
1225
1226 // Serialize subfield values and refresh the fonts in the preview window.
1227 update_font_value( wrapper );
1228
1229 wp.customize.previewer.send( 'font-changed' );
1230 } );
1231 }
1232
1233 // When the previewer window is ready, render the fonts
1234 var self = this;
1235 wp.customize.previewer.bind( 'ready', function() {
1236 self.render_fonts();
1237 } );
1238
1239 // Handle the reverse value direction, when the customize setting is updated and the subfields need to update their values.
1240 $fontFamilyFields.each( function( i, el ) {
1241 let wrapper = $( el ).closest( wrapperSelector ),
1242 value_holder = wrapper.children( valueHolderSelector ),
1243 setting_id = $( value_holder ).data( 'customize-setting-link' ),
1244 setting = wp.customize( setting_id );
1245
1246 setting.bind( function( newValue, oldValue ) {
1247 if ( ! updatingValue[this.id] ) {
1248 value_holder.val( newValue );
1249
1250 load_font_value( wrapper );
1251 }
1252 } )
1253 } )
1254 }
1255
1256 /**
1257 * This function updates the data in font weight selector from the given <option> element
1258 *
1259 * @param option
1260 * @param wraper
1261 */
1262 function update_weight_field( option, wraper ) {
1263 let variants = $( option ).data( 'variants' ),
1264 font_weights = wraper.find( fontWeightSelector ),
1265 selected_variant = font_weights.data( 'default' ),
1266 new_variants = [],
1267 id = wraper.find( valueHolderSelector ).data( 'customizeSettingLink' );
1268
1269 variants = maybeJsonParse( variants );
1270
1271 if ( customify_settings.settings[id].load_all_weights || typeof variants === "undefined" || Object.keys( variants ).length < 2 ) {
1272 font_weights.parent().hide();
1273 } else {
1274 font_weights.parent().show();
1275 }
1276
1277 // we need to turn the data array into a specific form like [{id:"id", text:"Text"}]
1278 $.each( variants, function( index, variant ) {
1279 new_variants[index] = {
1280 'id': variant,
1281 'text': variant
1282 };
1283
1284 if ( selected_variant == variant ) {
1285 new_variants[index].selected = true;
1286 }
1287 } );
1288
1289 // We need to clear the old select2 field and reinitialize it.
1290 $( font_weights ).select2().empty();
1291 $( font_weights ).select2( {
1292 data: new_variants
1293 } ).on( 'change', function( e ) {
1294 let wrapper = $( e.target ).closest( wrapperSelector );
1295
1296 // Serialize subfield values and refresh the fonts in the preview window.
1297 update_font_value( wrapper );
1298 } );
1299 }
1300
1301 /**
1302 * This function updates the data in font subset selector from the given <option> element
1303 * @param option
1304 * @param wraper
1305 */
1306 function update_subset_field( option, wraper ) {
1307 let subsets = $( option ).data( 'subsets' ),
1308 font_subsets = wraper.find( fontSubsetsSelector ),
1309 new_subsets = [],
1310 type = $( option ).data( 'type' );
1311
1312 if ( type !== 'google' ) {
1313 font_subsets.parent().hide();
1314 return;
1315 }
1316
1317 let current_value = wraper.children( valueHolderSelector ).val();
1318
1319 current_value = maybeJsonParse( current_value );
1320 if ( _.isUndefined( current_value.selected_subsets ) ) {
1321 return;
1322 }
1323 current_value = current_value.selected_subsets;
1324
1325 subsets = maybeJsonParse( subsets );
1326
1327 if ( Object.keys( subsets ).length < 2 ) {
1328 font_subsets.parent().hide();
1329 } else {
1330 font_subsets.parent().show();
1331 }
1332
1333 // we need to turn the data array into a specific form like [{id:"id", text:"Text"}]
1334 $.each( subsets, function( index, subset ) {
1335 new_subsets[index] = {
1336 'id': subset,
1337 'text': subset
1338 };
1339
1340 // current_subsets
1341 if ( typeof current_value !== 'undefined' && current_value !== null && current_value.indexOf( subset ) !== - 1 ) {
1342 new_subsets[index].selected = true;
1343 }
1344 } );
1345
1346 // We need to clear the old select2 field and reinitialize it.
1347 $( font_subsets ).select2().empty();
1348 $( font_subsets ).select2( {
1349 data: new_subsets
1350 } ).on( 'change', function( e ) {
1351 let wrapper = $( e.target ).closest( wrapperSelector );
1352
1353 // Serialize subfield values and refresh the fonts in the preview window.
1354 update_font_value( wrapper );
1355 } );
1356 }
1357
1358 /**
1359 * This function is a custom value serializer for our entire font field
1360 * It collects values and saves them (encoded) into the `.customify_font_values` input's value
1361 */
1362 function update_font_value( wraper ) {
1363 let options_list = $( wraper ).find( '.font-options__options-list' ),
1364 inputs = options_list.find( '[data-field]' ),
1365 value_holder = wraper.children( valueHolderSelector ),
1366 setting_id = $( value_holder ).data( 'customize-setting-link' ),
1367 setting = wp.customize( setting_id ),
1368 newFontData = {};
1369
1370 // If we are already self-updating this and we haven't finished, we need to stop here to prevent infinite loops
1371 // This call might have come from a subfield detecting the change the triggering a further update_font_value()
1372 if ( true === updatingValue[setting_id] ) {
1373 return;
1374 }
1375
1376 // If we are loading this setting value and haven't finished, there is no point in updating it as this would cause infinite loops.
1377 if ( true === loadingValue[setting_id] ) {
1378 return;
1379 }
1380
1381 // Mark the fact that we are self-updating the field value
1382 updatingValue[setting_id] = true;
1383
1384 inputs.each( function( key, el ) {
1385 let field = $( el ).data( 'field' ),
1386 value = $( el ).val();
1387
1388 if ( 'font_family' === field ) {
1389 // the font family also holds the type
1390 let selected_opt = $( el.options[el.selectedIndex] ),
1391 type = selected_opt.data( 'type' ),
1392 subsets = selected_opt.data( 'subsets' ),
1393 variants = selected_opt.data( 'variants' );
1394
1395 if ( ! _.isUndefined( type ) ) {
1396 newFontData['type'] = type;
1397 if ( type === 'theme_font' ) {
1398 newFontData['src'] = selected_opt.data( 'src' );
1399 }
1400 }
1401
1402 if ( ! _.isUndefined( variants ) ) {
1403 newFontData['variants'] = maybeJsonParse( variants );
1404 }
1405
1406 if ( ! _.isUndefined( subsets ) ) {
1407 newFontData['subsets'] = maybeJsonParse( subsets );
1408 }
1409 }
1410
1411
1412 if ( ! _.isUndefined( field ) && ! _.isUndefined( value ) && ! _.isNull( value ) && value !== '' ) {
1413 newFontData[field] = value;
1414 }
1415 } );
1416
1417 // Serialize the newly gathered font data
1418 let serializedNewFontData = encodeValues( newFontData );
1419 // Set the serialized value in the hidden field.
1420 value_holder.val( serializedNewFontData );
1421 // Update also the Customizer setting value.
1422 setting.set( serializedNewFontData );
1423
1424
1425 // Finished with the field value self-updating.
1426 updatingValue[setting_id] = false;
1427
1428 return newFontData;
1429 }
1430
1431 /**
1432 * This function is a reverse of update_font_value(), initializing the entire font field controls based on the value stored in the hidden input.
1433 */
1434 function load_font_value( wrapper ) {
1435 let options_list = $( wrapper ).find( '.font-options__options-list' ),
1436 inputs = options_list.find( '[data-field]' ),
1437 value_holder = wrapper.children( valueHolderSelector ),
1438 value = maybeJsonParse( value_holder.val() ),
1439 setting_id = $( value_holder ).data( 'customize-setting-link' );
1440
1441 // If we are already loading this setting value and haven't finished, there is no point in starting again.
1442 if ( true === loadingValue[setting_id] ) {
1443 return;
1444 }
1445
1446 // Mark the fact that we are loading the field value
1447 loadingValue[setting_id] = true;
1448
1449 inputs.each( function( key, el ) {
1450 let field = $( el ).data( 'field' );
1451
1452 // In the case of select2, only the original selects have the data field, thus excluding select2 created select DOM elements
1453 if ( typeof field !== "undefined" && field !== "" && typeof value[field] !== "undefined" ) {
1454 $( el ).val( value[field] ).trigger( 'change' );
1455 }
1456 } );
1457
1458 // Finished with the field value loading.
1459 loadingValue[setting_id] = false;
1460 }
1461
1462 var maybeJsonParse = function( value ) {
1463 let parsed;
1464
1465 //try and parse it, with decodeURIComponent
1466 try {
1467 parsed = JSON.parse( decodeURIComponent( value ) );
1468 } catch ( e ) {
1469
1470 // in case of an error, treat is as a string
1471 parsed = value;
1472 }
1473
1474 return parsed;
1475 };
1476
1477 function encodeValues( obj ) {
1478 return encodeURIComponent( JSON.stringify( obj ) );
1479 }
1480
1481 function render_fonts() {
1482 $( '.customify_font_family' ).select2().trigger( 'change' )
1483 }
1484
1485 return {
1486 render_fonts: render_fonts,
1487 init: init,
1488 update_font_value: update_font_value
1489 };
1490 }
1491 )();
1492
1493 var Queue = function() {
1494 var lastPromise = null;
1495 var queueDeferred = null;
1496 var methodDeferred = null;
1497
1498 this.add_steps = function( key, steps, args ) {
1499 var self = this;
1500 this.methodDeferred = $.Deferred();
1501 this.queueDeferred = this.setup();
1502
1503 $.each( steps, function( i, step ) {
1504 self.queue( key, step );
1505 } );
1506 };
1507
1508 this.process_remote_step = function( key, data, step ) {
1509 var self = this;
1510
1511 if ( _.isUndefined( data ) || _.isNull( data ) ) {
1512 return false;
1513 }
1514
1515 var new_step = step;
1516 $.each( data, function( i, k ) {
1517 debugger;
1518 // prepare data for new requests
1519 new_step.recall_data = k.data;
1520 new_step.recall_type = k.type;
1521 new_step.type = 'recall';
1522
1523 self.queue( key, new_step, k.id );
1524 } );
1525 };
1526
1527 this.log_action = function( action, key, msg ) {
1528 if ( action === 'start' ) {
1529 $( '.wpGrade-import-results' ).show();
1530 $( '.wpGrade-import-results' ).append( '<span class="import_step_note imports_step_' + key + '" ><span class="step_info" data-balloon="Working on it" data-balloon-pos="up"></span>Importing ' + key + '</span>' );
1531 } else if ( action === 'end' ) {
1532 var $notice = $( '.imports_step_' + key + ' .step_info' );
1533
1534 if ( $notice.length > 0 || msg !== "undefined" ) {
1535 $notice.attr( 'data-balloon', msg );
1536 $notice.addClass( 'success' );
1537 } else {
1538 $notice.attr( 'data-balloon', 'Done' );
1539 $notice.addClass( 'failed' );
1540 }
1541 }
1542 };
1543
1544 this.queue = function( key, data, step_key ) {
1545 var self = this;
1546 if ( ! _.isUndefined( step_key ) ) {
1547 this.log_action( 'start', step_key );
1548 }
1549
1550 // execute next queue method
1551 this.queueDeferred.done( this.request( key, data, step_key ) );
1552 lastPromise = self.methodDeferred.promise();
1553 };
1554
1555 this.request = function( key, step, step_key ) {
1556 var self = this;
1557 // call actual method and wrap output in deferred
1558 //setTimeout( function() {
1559 var data_args = {
1560 action: 'customify_import_step',
1561 step_id: step.id,
1562 step_type: step.type,
1563 option_key: key
1564 };
1565
1566 if ( ! _.isUndefined( step.recall_data ) ) {
1567 data_args.recall_data = step.recall_data;
1568 }
1569
1570 if ( ! _.isUndefined( step.recall_type ) ) {
1571 data_args.recall_type = step.recall_type;
1572 }
1573
1574 $.ajax( {
1575 url: customify_settings.import_rest_url + 'customify/1.0/import',
1576 method: 'POST',
1577 beforeSend: function( xhr ) {
1578 xhr.setRequestHeader( 'X-WP-Nonce', WP_API_Settings.nonce );
1579 },
1580 dataType: 'json',
1581 contentType: 'application/x-www-form-urlencoded; charset=UTF-8',
1582 data: data_args
1583 } ).done( function( response ) {
1584 if ( ! _.isUndefined( response.success ) && response.success ) {
1585 var results = response.data;
1586 if ( step.type === 'remote' ) {
1587 self.process_remote_step( key, results, step );
1588 }
1589 }
1590
1591 if ( ! _.isUndefined( step_key ) && ! _.isUndefined( response.message ) ) {
1592 self.log_action( 'end', step_key, response.message );
1593 }
1594 } );
1595
1596 self.methodDeferred.resolve();
1597 //}, 3450 );
1598 };
1599
1600 this.setup = function() {
1601 var self = this;
1602
1603 self.queueDeferred = $.Deferred();
1604
1605 // when the previous method returns, resolve this one
1606 $.when( lastPromise ).always( function() {
1607 self.queueDeferred.resolve();
1608 } );
1609
1610 return self.queueDeferred.promise();
1611 }
1612 };
1613
1614 /** HELPERS **/
1615
1616 /**
1617 * Function to check if a value exists in an object
1618 * @param value
1619 * @param obj
1620 * @returns {boolean}
1621 */
1622 var inObject = function( value, obj ) {
1623 for ( var k in obj ) {
1624 if ( ! obj.hasOwnProperty( k ) ) {
1625 continue;
1626 }
1627 if ( _.isEqual( obj[k], value ) ) {
1628 return true;
1629 }
1630 }
1631 return false;
1632 };
1633
1634 var maybeJsonParse = function( value ) {
1635 var parsed;
1636
1637 //try and parse it, with decodeURIComponent
1638 try {
1639 parsed = JSON.parse( decodeURIComponent( value ) );
1640 } catch ( e ) {
1641
1642 // in case of an error, treat is as a string
1643 parsed = value;
1644 }
1645
1646 return parsed;
1647 };
1648
1649 var getUrlVars = function( name ) {
1650 var vars = [], hash;
1651 var hashes = window.location.href.slice( window.location.href.indexOf( '?' ) + 1 ).split( '&' );
1652 for ( var i = 0; i < hashes.length; i ++ ) {
1653 hash = hashes[i].split( '=' );
1654
1655 vars.push( hash[0] );
1656 vars[hash[0]] = hash[1];
1657 }
1658
1659 if ( ! _.isUndefined( vars[name] ) ) {
1660 return vars[name];
1661 }
1662 return false;
1663 };
1664
1665 var isJsonString = function( str ) {
1666 try {
1667 JSON.parse( str );
1668 } catch ( e ) {
1669 return false;
1670 }
1671 return true;
1672 };
1673 }
1674 )( jQuery, window, wp );
1675
1676
1677 // Reverses a hex color to either black or white
1678 function customifyInverseHexColorToBlackOrWhite( hex ) {
1679 return customifyInverseHexColor( hex, true );
1680 }
1681
1682 // Taken from here: https://stackoverflow.com/a/35970186/6260836
1683 function customifyInverseHexColor( hex, bw ) {
1684 if ( hex.indexOf( '#' ) === 0 ) {
1685 hex = hex.slice( 1 );
1686 }
1687 // convert 3-digit hex to 6-digits.
1688 if ( hex.length === 3 ) {
1689 hex = hex[0] + hex[0] + hex[1] + hex[1] + hex[2] + hex[2];
1690 }
1691 if ( hex.length !== 6 ) {
1692 throw new Error( 'Invalid HEX color.' );
1693 }
1694 var r = parseInt( hex.slice( 0, 2 ), 16 ),
1695 g = parseInt( hex.slice( 2, 4 ), 16 ),
1696 b = parseInt( hex.slice( 4, 6 ), 16 );
1697 if ( bw ) {
1698 // http://stackoverflow.com/a/3943023/112731
1699 return (
1700 r * 0.299 + g * 0.587 + b * 0.114
1701 ) > 186
1702 ? '#000000'
1703 : '#FFFFFF';
1704 }
1705 // invert color components
1706 r = (
1707 255 - r
1708 ).toString( 16 );
1709 g = (
1710 255 - g
1711 ).toString( 16 );
1712 b = (
1713 255 - b
1714 ).toString( 16 );
1715 // pad each with zeros and return
1716 return "#" + customifyPadZero( r ) + customifyPadZero( g ) + customifyPadZero( b );
1717 }
1718
1719 function customifyPadZero( str, len ) {
1720 len = len || 2;
1721 var zeros = new Array( len ).join( '0' );
1722 return (
1723 zeros + str
1724 ).slice( - len );
1725 }
1726
1727 // Shading, Blending and Converting colors
1728 // Taken from here: https://github.com/PimpTrizkit/PJs/wiki/12.-Shade,-Blend-and-Convert-a-Web-Color-(pSBC.js)
1729 const pSBC = function( p, from, to ) {
1730 if ( typeof(
1731 p
1732 ) != "number" || p < - 1 || p > 1 || typeof(
1733 from
1734 ) != "string" || (
1735 from[0] != 'r' && from[0] != '#'
1736 ) || (
1737 to && typeof(
1738 to
1739 ) != "string"
1740 ) ) {
1741 return null;
1742 } //ErrorCheck
1743 if ( ! this.pSBCr ) {
1744 this.pSBCr = ( d ) => {
1745 let l = d.length, RGB = {};
1746 if ( l > 9 ) {
1747 d = d.split( "," );
1748 if ( d.length < 3 || d.length > 4 ) {
1749 return null;
1750 }//ErrorCheck
1751 RGB[0] = i( d[0].split( "(" )[1] ), RGB[1] = i( d[1] ), RGB[2] = i( d[2] ), RGB[3] = d[3] ? parseFloat( d[3] ) : - 1;
1752 } else {
1753 if ( l == 8 || l == 6 || l < 4 ) {
1754 return null;
1755 } //ErrorCheck
1756 if ( l < 6 ) {
1757 d = "#" + d[1] + d[1] + d[2] + d[2] + d[3] + d[3] + (
1758 l > 4 ? d[4] + "" + d[4] : ""
1759 );
1760 } //3 or 4 digit
1761 d = i( d.slice( 1 ), 16 ), RGB[0] = d >> 16 & 255, RGB[1] = d >> 8 & 255, RGB[2] = d & 255, RGB[3] = - 1;
1762 if ( l == 9 || l == 5 ) {
1763 RGB[3] = r( (
1764 RGB[2] / 255
1765 ) * 10000 ) / 10000, RGB[2] = RGB[1], RGB[1] = RGB[0], RGB[0] = d >> 24 & 255;
1766 }
1767 }
1768 return RGB;
1769 }
1770 }
1771 var i = parseInt, r = Math.round, h = from.length > 9, h = typeof(
1772 to
1773 ) == "string" ? to.length > 9 ? true : to == "c" ? ! h : false : h, b = p < 0, p = b ? p * - 1 : p,
1774 to = to && to != "c" ? to : b ? "#000000" : "#FFFFFF", f = this.pSBCr( from ), t = this.pSBCr( to );
1775 if ( ! f || ! t ) {
1776 return null;
1777 } //ErrorCheck
1778 if ( h ) {
1779 return "rgb" + (
1780 f[3] > - 1 || t[3] > - 1 ? "a(" : "("
1781 ) + r( (
1782 t[0] - f[0]
1783 ) * p + f[0] ) + "," + r( (
1784 t[1] - f[1]
1785 ) * p + f[1] ) + "," + r( (
1786 t[2] - f[2]
1787 ) * p + f[2] ) + (
1788 f[3] < 0 && t[3] < 0 ? ")" : "," + (
1789 f[3] > - 1 && t[3] > - 1 ? r( (
1790 (
1791 t[3] - f[3]
1792 ) * p + f[3]
1793 ) * 10000 ) / 10000 : t[3] < 0 ? f[3] : t[3]
1794 ) + ")"
1795 );
1796 } else {
1797 return "#" + (
1798 0x100000000 + r( (
1799 t[0] - f[0]
1800 ) * p + f[0] ) * 0x1000000 + r( (
1801 t[1] - f[1]
1802 ) * p + f[1] ) * 0x10000 + r( (
1803 t[2] - f[2]
1804 ) * p + f[2] ) * 0x100 + (
1805 f[3] > - 1 && t[3] > - 1 ? r( (
1806 (
1807 t[3] - f[3]
1808 ) * p + f[3]
1809 ) * 255 ) : t[3] > - 1 ? r( t[3] * 255 ) : f[3] > - 1 ? r( f[3] * 255 ) : 255
1810 )
1811 ).toString( 16 ).slice( 1, f[3] > - 1 || t[3] > - 1 ? undefined : - 2 );
1812 }
1813 };