PluginProbe
Customify / 1.6.0
Customify v1.6.0
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.6.0, at js/customizer.js

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