PluginProbe
Customify / 2.10.1
Customify v2.10.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
← All changes | js/customizer_preview.js +686 -233 1.7.1 → 2.10.1 View file →
@@ -1,299 +1,752 @@
1 -;(function ($, window, document, undefined) {
1 +;(function ($, window, document) {
2 2
3 - var fonts_cache = [];
3 + /* global customify.config */
4 + /* global WebFont */
4 5
5 - $(document).ready(function () {
6 - var api = parent.wp.customize,
7 - wp_settings = api.settings.settings;
6 + $(window).on('load', function () {
7 + // We need to do this on window.load because on document.ready might be too early.
8 + maybeLoadWebfontloaderScript()
9 + })
8 10
9 - load_webfont_once();
11 + const maybeLoadWebfontloaderScript = function () {
12 + if (typeof WebFont === 'undefined') {
13 + let tk = document.createElement('script')
14 + tk.src = parent.customify.config.webfontloader_url
15 + tk.type = 'text/javascript'
16 + let s = document.getElementsByTagName('script')[0]
17 + s.parentNode.insertBefore(tk, s)
18 + }
19 + }
10 20
11 - $.each(customify_settings.settings, function (key, el) {
21 + const fontsCache = []
12 22
13 - if (el.type === "font") {
14 - api(key, function (setting) {
15 - setting.bind(function (to) {
16 - let $values = maybeJsonParse(to);
23 + // Do everything at document.ready
24 + $(function () {
25 + const api = parent.wp.customize
26 + const customify = parent.customify
27 + const apiSettings = api.settings.settings
28 + const regexForMultipleReplace = new RegExp('-', 'g')
17 29
18 - if (typeof $values.font_family !== "undefined") {
19 - maybeLoadFontFamily($values);
20 - }
30 + $.each(customify.config.settings, function (key, settingConfig) {
31 + const propertiesPrefix = typeof settingConfig.properties_prefix === 'undefined' ? '' : settingConfig.properties_prefix
32 + if (settingConfig.type === 'font') {
33 + api(key, function (setting) {
34 + setting.bind(function (newValue) {
35 + if (typeof newValue === 'undefined') {
36 + return
37 + }
21 38
22 - let vls = get_CSS_values(this.id, $values),
23 - CSS = get_CSS_code(this.id, vls),
24 - field_style = $('#customify_font_output_for_' + el.html_safe_option_id);
39 + if (typeof newValue.font_family !== 'undefined') {
40 + maybeLoadFontFamily(newValue, this.id)
41 + }
25 42
26 - field_style.html(CSS);
27 - });
28 - });
43 + const $styleElement = $('#customify_font_output_for_' + settingConfig.html_safe_option_id)
44 + if (!$styleElement.length) {
45 + return
46 + }
29 47
30 - } else if (typeof wp_settings[key] !== "undefined" && typeof el.css !== "undefined" && typeof el.live !== 'undefined' && el.live === true) {
31 - api(key, function (setting) {
48 + const cssValue = getFontFieldCSSValue(this.id, newValue)
49 + if (_.isEmpty(cssValue)) {
50 + // Empty the style element.
51 + $styleElement.html('')
52 + return
53 + }
32 54
33 - setting.bind(function (to) {
55 + $styleElement.html(getFontFieldCSSCode(this.id, cssValue, propertiesPrefix, newValue))
56 + })
57 + })
34 58
35 - $.each(el.css, function (counter, property_config) {
36 - var properties = [];
59 + } else if (typeof apiSettings !== 'undefined'
60 + && typeof apiSettings[key] !== 'undefined'
61 + && typeof settingConfig.css !== 'undefined'
62 + && typeof settingConfig.live !== 'undefined'
63 + && settingConfig.live === true) {
37 64
38 - properties[property_config.property] = property_config.selector;
39 - if (typeof property_config.callback_filter !== "undefined") {
40 - properties['callback'] = property_config.callback_filter;
41 - }
65 + api(key, function (setting) {
66 + setting.bind(function (newValue) {
42 67
43 - let css_update_args = {
44 - properties: properties,
45 - propertyValue: to
46 - };
68 + $.each(settingConfig.css, function (idx, propertyConfig) {
69 + // Replace all dashes with underscores thus making the CSS property safe to us in a HTML ID.
70 + const $styleElement = $('.dynamic_setting_' + settingConfig.html_safe_option_id + '_property_' + propertyConfig.property.replace(regexForMultipleReplace, '_') + '_' + idx)
71 + if (!$styleElement.length) {
72 + return
73 + }
47 74
48 - if (typeof this.unit !== 'undefined') {
49 - css_update_args.unit = this.unit;
50 - }
75 + const properties = {}
76 + if (typeof propertyConfig.property !== 'undefined' && typeof propertyConfig.selector !== 'undefined') {
77 + properties[propertyConfig.property] = propertyConfig.selector
78 + }
79 + if (typeof propertyConfig.callback_filter !== 'undefined') {
80 + properties['callback'] = propertyConfig.callback_filter
81 + }
82 + if (_.isEmpty(properties)) {
83 + return
84 + }
51 85
52 - // Replace all dashes with underscores thus making the CSS property safe to us in a HTML ID.
53 - let regex_for_multiple_replace = new RegExp('-', 'g'),
54 - cssStyleSelector = '.dynamic_setting_' + el.html_safe_option_id + '_property_' + property_config.property.replace(regex_for_multiple_replace, '_');
86 + const cssUpdateArgs = {
87 + properties: properties,
88 + propertyValue: newValue,
89 + negative_value: propertyConfig.hasOwnProperty('negative_value') ? propertyConfig['negative_value'] : false
90 + }
55 91
56 - $(cssStyleSelector).cssUpdate(css_update_args);
57 - });
92 + if (typeof this.unit !== 'undefined') {
93 + cssUpdateArgs.unit = this.unit
94 + }
58 95
59 - });
60 - });
61 - } else if (typeof el.live === "object" && el.live.length > 0) {
62 - // if the live parameter is an object it means that is a list of css classes
63 - // these classes should be affected by the change of the text fields
64 - var field_class = el.live.join();
96 + $styleElement.cssUpdate(cssUpdateArgs)
97 + })
65 98
66 - // if this field is allowed to modify text then we'll edit this live
67 - if ($.inArray(el.type, ['text', 'textarea', 'ace_editor']) > -1) {
68 - wp.customize(key, function (value) {
69 - value.bind(function (text) {
70 - let sanitizer = document.createElement('div');
99 + })
100 + })
101 + } else if (typeof settingConfig.live === 'object' && settingConfig.live.length > 0) {
102 + // If the live parameter is an object it means that this is a list of css classes.
103 + // These classes should be affected by the change of the text fields.
104 + const fieldClass = settingConfig.live.join()
71 105
72 - sanitizer.innerHTML = text;
73 - $(field_class).html(text);
74 - });
75 - });
76 - }
77 - }
78 - });
106 + // if this field is allowed to modify text then we'll edit this live
107 + if ($.inArray(settingConfig.type, ['text', 'textarea', 'ace_editor']) > -1) {
108 + api(key, function (value) {
109 + value.bind(function (text) {
110 + let sanitizer = document.createElement('div')
79 111
80 - /** Bind Custom Events **/
112 + sanitizer.innerHTML = text
113 + $(fieldClass).html(text)
114 + })
115 + })
116 + }
117 + }
118 + })
81 119
82 - // api.previewer.bind('highlight',function(e){
83 - // $('.customizerHighlight').removeClass('customizerHighlight');
84 - //
85 - // if ( $(e).length > 0 ) {
86 - // $(e).each(function(){
87 - // $(this).addClass('customizerHighlight');
88 - // });
89 - // }
90 - // });
120 + /**
121 + * HELPERS
122 + **/
91 123
92 - api('live_css_edit', function (setting) {
93 - setting.bind(function (new_text) {
94 - $('#customify_css_editor_output').text(new_text);
95 - });
96 - });
124 + // Mirror logic of server-side Customify_Fonts_Global::getCSSValue()
125 + const getFontFieldCSSValue = function (settingID, value) {
97 126
127 + const CSSValue = {}
98 128
99 - /*** HELPERS **/
129 + if (typeof value.font_family !== 'undefined' && !_.includes(['','false',false], value.font_family)) {
130 + CSSValue['font-family'] = value.font_family
131 + // "Expand" the font family by appending the fallback stack, if any is available.
132 + // But only do this, if the value is not already a font stack!
133 + if (CSSValue['font-family'].indexOf(',') === -1) {
134 + const fallbackStack = getFontFamilyFallbackStack(CSSValue['font-family'])
135 + if (fallbackStack.length) {
136 + CSSValue['font-family'] += ',' + fallbackStack
137 + }
138 + }
100 139
101 - function load_webfont_once() {
102 - if (typeof WebFont === "undefined") {
103 - var tk = document.createElement('script');
104 - tk.src = '//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
105 - tk.type = 'text/javascript';
106 - var s = document.getElementsByTagName('script')[0];
107 - s.parentNode.insertBefore(tk, s);
108 - }
109 - }
140 + CSSValue['font-family'] = sanitizeFontFamilyCSSValue(CSSValue['font-family'])
141 + }
110 142
111 - var get_CSS_values = function (ID, $values) {
143 + if (typeof value.font_variant !== 'undefined' && !_.includes(['','false',false], value.font_variant)) {
144 + let variant = value.font_variant
112 145
113 - var store = {};
146 + if (_.isString(variant)) {
147 + // We may have a style in the variant; attempt to split.
148 + if (variant.indexOf('italic') !== -1) {
149 + CSSValue['font-style'] = 'italic'
150 + variant = variant.replace('italic', '')
151 + } else if (variant.indexOf('oblique') !== -1) {
152 + CSSValue['font-style'] = 'oblique'
153 + variant = variant.replace('oblique', '')
154 + }
114 155
115 - if (typeof $values.font_family !== "undefined") {
116 - store['font-family'] = $values.font_family;
117 - }
156 + // If anything remained, then we have a font weight also.
157 + if (variant !== '') {
158 + if (variant === 'regular' || variant === 'normal') {
159 + variant = '400'
160 + }
118 161
119 - if (typeof $values.selected_variants !== "undefined") {
162 + CSSValue['font-weight'] = variant
163 + }
164 + } else if (_.isNumeric(variant)) {
165 + CSSValue['font-weight'] = String(variant);
166 + }
167 + }
120 168
121 - var variants = null;
169 + if (typeof value.font_size !== 'undefined' && !_.includes(['','false',false], value.font_size)) {
170 + let fontSizeUnit = false
122 171
123 - if (typeof $values.selected_variants !== "undefined" && $values.selected_variants !== null) {
124 - variants = $values.selected_variants;
125 - } else if (typeof $values.variants !== "undefined" && typeof $values.variants[0] !== "undefined") {
126 - variants = $values.variants[0];
127 - }
172 + CSSValue['font-size'] = value.font_size
173 + // If the value already contains a unit (is not numeric), go with that.
174 + if (isNaN(value.font_size)) {
175 + // If we have a standardized value field (as array), use that.
176 + if (typeof value.font_size.value !== 'undefined') {
177 + CSSValue['font-size'] = value.font_size.value
178 + if (typeof value.font_size.unit !== 'undefined') {
179 + fontSizeUnit = value.font_size.unit
180 + }
181 + } else {
182 + fontSizeUnit = getFieldUnit(settingID, 'font-size')
183 + }
184 + } else {
185 + fontSizeUnit = getFieldUnit(settingID, 'font-size')
186 + }
128 187
129 - // google fonts also have the italic string inside, split that
130 - if (variants !== null && variants.indexOf('italic') !== -1) {
131 - store['font-style'] = 'italic';
132 - variants = variants.replace('italic', '');
133 - }
188 + if (false !== fontSizeUnit) {
189 + CSSValue['font-size'] += fontSizeUnit
190 + }
191 + }
134 192
135 - if (variants !== "") {
136 - if (variants === 'regular') {
137 - variants = 'normal';
138 - }
193 + if (typeof value.letter_spacing !== 'undefined' && !_.includes(['','false',false], value.letter_spacing)) {
194 + let letterSpacingUnit = false
139 195
140 - store['font-weight'] = variants;
141 - }
142 - }
196 + CSSValue['letter-spacing'] = value.letter_spacing
197 + // If the value already contains a unit (is not numeric), go with that.
198 + if (isNaN(value.letter_spacing)) {
199 + // If we have a standardized value field (as array), use that.
200 + if (typeof value.letter_spacing.value !== 'undefined') {
201 + CSSValue['letter-spacing'] = value.letter_spacing.value
202 + if (typeof value.letter_spacing.unit !== 'undefined') {
203 + letterSpacingUnit = value.letter_spacing.unit
204 + }
205 + } else {
206 + letterSpacingUnit = getFieldUnit(settingID, 'letter-spacing')
207 + }
208 + } else {
209 + letterSpacingUnit = getFieldUnit(settingID, 'letter-spacing')
210 + }
143 211
144 - if (typeof $values.font_size !== "undefined") {
145 - store['font-size'] = $values.font_size + get_field_unit(ID, 'font-size');
146 - }
212 + if (false !== letterSpacingUnit) {
213 + CSSValue['letter-spacing'] += letterSpacingUnit
214 + }
215 + }
147 216
148 - if (typeof $values.letter_spacing !== "undefined") {
149 - store['letter-spacing'] = $values.letter_spacing + get_field_unit(ID, 'letter-spacing');
150 - }
217 + if (typeof value.line_height !== 'undefined' && !_.includes(['','false',false], value.line_height)) {
218 + let lineHeightUnit = false
151 219
152 - if (typeof $values.line_height !== "undefined") {
153 - store['line-height'] = $values.line_height + get_field_unit(ID, 'line-height');
154 - }
220 + CSSValue['line-height'] = value.line_height
221 + // If the value already contains a unit (is not numeric), go with that.
222 + if (isNaN(value.line_height)) {
223 + // If we have a standardized value field (as array), use that.
224 + if (typeof value.line_height.value !== 'undefined') {
225 + CSSValue['line-height'] = value.line_height.value
226 + if (typeof value.line_height.unit !== 'undefined') {
227 + lineHeightUnit = value.line_height.unit
228 + }
229 + } else {
230 + lineHeightUnit = getFieldUnit(settingID, 'line-height')
231 + }
232 + } else {
233 + lineHeightUnit = getFieldUnit(settingID, 'line-height')
234 + }
155 235
156 - if (typeof $values.text_align !== "undefined") {
157 - store['text-align'] = $values.text_align;
158 - }
236 + if (false !== lineHeightUnit) {
237 + CSSValue['line-height'] += lineHeightUnit
238 + }
239 + }
159 240
160 - if (typeof $values.text_transform !== "undefined") {
161 - store['text-transform'] = $values.text_transform;
162 - }
163 - if (typeof $values.text_decoration !== "undefined") {
164 - store['text-decoration'] = $values.text_decoration;
165 - }
241 + if (typeof value.text_align !== 'undefined' && !_.includes(['','false',false], value.text_align)) {
242 + CSSValue['text-align'] = value.text_align
243 + }
166 244
167 - return store;
168 - };
245 + if (typeof value.text_transform !== 'undefined' && !_.includes(['','false',false], value.text_transform)) {
246 + CSSValue['text-transform'] = value.text_transform
247 + }
169 248
170 - var get_CSS_code = function (ID, $values) {
249 + if (typeof value.text_decoration !== 'undefined' && !_.includes(['','false',false], value.text_decoration)) {
250 + CSSValue['text-decoration'] = value.text_decoration
251 + }
171 252
172 - var field = customify_settings.settings[ID];
173 - var output = '';
253 + return CSSValue
254 + }
174 255
175 - if (typeof window !== "undefined" && typeof field.callback !== "undefined" && typeof window[field.callback] === "function") {
176 - output = window[field.callback]($values, field);
177 - } else {
178 - output = field.selector + "{\n";
179 - $.each($values, function (k, v) {
180 - output += k + ': ' + v + ";\n";
181 - })
182 - output += "}\n";
183 - }
256 + // Mirror logic of server-side Customify_Fonts_Global::getFontStyle()
257 + const getFontFieldCSSCode = function (settingID, cssValue, prefix, value) {
258 + const fontConfig = customify.config.settings[settingID]
259 + let output = ''
184 260
185 - return output;
186 - };
261 + if (typeof window !== 'undefined' && typeof fontConfig.callback !== 'undefined' && typeof window[fontConfig.callback] === 'function') {
262 + // The callbacks expect a string selector right now, not a standardized list.
263 + // @todo Maybe migrate all callbacks to the new standardized data and remove all this.
264 + const plainSelectors = []
265 + _.each(fontConfig.selector, function (details, selector) {
266 + plainSelectors.push(selector)
267 + })
268 + const adjustedFontConfig = $.extend(true,{},fontConfig)
269 + adjustedFontConfig.selector = plainSelectors.join(', ')
187 270
188 - var get_field_unit = function (ID, field) {
189 - var unit = 'px';
190 - if (typeof customify_settings.settings[ID] === "undefined" || typeof customify_settings.settings[ID].fields[field] === "undefined") {
191 - return unit;
192 - }
271 + // Also, "kill" all fields unit since we pass final CSS values.
272 + // @todo For some reason, the client-side Typeline cbs are not consistent and expect the font-size value with unit.
273 + _.each(adjustedFontConfig['fields'], function(fieldValue, fieldKey) {
274 + if (typeof fieldValue.unit !== 'undefined') {
275 + adjustedFontConfig['fields'][fieldKey]['unit'] = false;
276 + }
277 + })
193 278
194 - if (typeof customify_settings.settings[ID].fields[field].unit !== "undefined") {
195 - return customify_settings.settings[ID].fields[field].unit;
196 - } else if (typeof customify_settings.settings[ID].fields[field][3] !== "undefined") {
197 - // in case of an associative array
198 - return customify_settings.settings[ID].fields[field][3];
199 - }
200 - }
279 + // Callbacks want the value keys with underscores, not dashes.
280 + // We will provide them in both versions for a smoother transition.
281 + _.each(cssValue, function (propertyValue, property) {
282 + const newKey = property.replace(regexForMultipleReplace, '_')
283 + cssValue[newKey] = propertyValue
284 + })
201 285
202 - var maybeLoadFontFamily = function (font) {
286 + return window[fontConfig.callback](cssValue, adjustedFontConfig)
287 + }
203 288
204 - if (typeof WebFont === "undefined") {
205 - var tk = document.createElement('script');
206 - tk.src = '//ajax.googleapis.com/ajax/libs/webfont/1/webfont.js';
207 - tk.type = 'text/javascript';
208 - var s = document.getElementsByTagName('script')[0];
209 - s.parentNode.insertBefore(tk, s);
210 - }
289 + if (typeof fontConfig.selector === 'undefined' || _.isEmpty(fontConfig.selector) || _.isEmpty(cssValue)) {
290 + return output
291 + }
211 292
212 - if (font.type === 'theme_font') {
213 - WebFont.load({
214 - custom: {
215 - families: [font.font_family],
216 - urls: [font.src]
217 - }
218 - });
219 - } else if (font.type === 'google') {
220 - var family = font.font_family,
221 - variants = null,
222 - subsets = null;
293 + // The general CSS allowed properties.
294 + const subFieldsCSSAllowedProperties = extractAllowedCSSPropertiesFromFontFields(fontConfig['fields'])
223 295
224 - if (typeof font.variants !== "undefined") {
225 - variants = maybeJsonParse(font.variants);
296 + // The selector is standardized to a list of simple string selectors, or a list of complex selectors with details.
297 + // In either case, the actual selector is in the key, and the value is an array (possibly empty).
226 298
227 - $.each(variants, function (k, v) {
299 + // Since we might have simple CSS selectors and complex ones (with special details),
300 + // for cleanliness we will group the simple ones under a single CSS rule,
301 + // and output individual CSS rules for complex ones.
302 + // Right now, for complex CSS selectors we are only interested in the `properties` sub-entry.
303 + const simpleCSSSelectors = []
304 + const complexCSSSelectors = {}
228 305
229 - if (k === "0") {
230 - family = family + ':';
231 - }
306 + _.each(fontConfig.selector, function (details, selector) {
307 + if (_.isEmpty(details.properties)) {
308 + // This is a simple selector.
309 + simpleCSSSelectors.push(selector)
310 + } else {
311 + complexCSSSelectors[selector] = details
312 + }
313 + })
232 314
233 - family = family + v;
315 + if (!_.isEmpty(simpleCSSSelectors)) {
316 + output += '\n' + simpleCSSSelectors.join(', ') + ' {\n'
317 + output += getFontFieldCSSProperties(cssValue, subFieldsCSSAllowedProperties, prefix)
318 + output += '}\n'
319 + }
234 320
235 - if ( Object.keys(variants).length > ( parseInt(k) + 1 ) ) {
236 - family = family + ',';
237 - } else if ( typeof font.selected_subsets !== "undefined" ) {
238 - // in case there is a subset selected, we need to separate it from the font weight
239 - family = family + ':';
240 - }
241 - });
242 - }
321 + if (!_.isEmpty(complexCSSSelectors)) {
322 + _.each(complexCSSSelectors, function (details, selector) {
323 + output += '\n' + selector + ' {\n'
324 + output += getFontFieldCSSProperties(cssValue, details.properties, prefix)
325 + output += '}\n'
326 + })
327 + }
243 328
244 - if (typeof font.selected_subsets !== "undefined") {
245 - subsets = maybeJsonParse(font.selected_subsets);
329 + return output
330 + }
246 331
247 - $.each(subsets, function (k, v) {
332 + // Mirror logic of server-side Customify_Fonts_Global::getCSSProperties()
333 + const getFontFieldCSSProperties = function (cssValue, allowedProperties = false, prefix = '') {
334 + let output = ''
248 335
249 - if ( k === "0" ) {
250 - family = family + ':';
251 - }
336 + $.each(cssValue, function (property, propertyValue) {
337 + // We don't want to output empty CSS rules.
338 + if ('' === propertyValue || false === propertyValue) {
339 + return
340 + }
252 341
253 - family = family + v;
342 + // If the property is not allowed, skip it.
343 + if (!isCSSPropertyAllowed(property, allowedProperties)) {
344 + return
345 + }
254 346
255 - if ( Object.keys(subsets).length > ( parseInt(k) + 1 ) ) {
256 - family = family + ',';
257 - }
258 - });
259 - }
347 + output += prefix + property + ': ' + propertyValue + ';\n'
348 + })
260 349
261 - if (fonts_cache.indexOf(family) === -1) {
262 - setTimeout(function(){
263 - WebFont.load({
264 - google: {families: [family]},
265 - classes: false,
266 - events: false,
267 - error: function (e) {
268 - console.log(e);
269 - },
270 - active: function () {
271 - sessionStorage.fonts = true;
272 - }
273 - });
274 - },10);
350 + return output
351 + }
275 352
276 - fonts_cache.push(family);
277 - }
353 + // Mirror logic of server-side Customify_Fonts_Global::isCSSPropertyAllowed()
354 + const isCSSPropertyAllowed = function (property, allowedProperties = false) {
355 + // Empty properties are not allowed.
356 + if (_.isEmpty(property)) {
357 + return false
358 + }
278 359
279 - } else {
280 - // else what?
281 - }
282 - }
360 + // Everything is allowed if nothing is specified.
361 + if (_.isEmpty(allowedProperties)) {
362 + return true
363 + }
283 364
284 - var maybeJsonParse = function (value) {
285 - var parsed;
365 + // For arrays
366 + if (_.includes(allowedProperties, property)) {
367 + return true
368 + }
286 369
287 - //try and parse it, with decodeURIComponent
288 - try {
289 - parsed = JSON.parse(decodeURIComponent(value));
290 - } catch (e) {
370 + // For objects
371 + if (_.has(allowedProperties, property) && allowedProperties[property]) {
372 + return true
373 + }
291 374
292 - // in case of an error, treat is as a string
293 - parsed = value;
294 - }
375 + return false
376 + }
295 377
296 - return parsed;
297 - };
298 - });
299 -})(jQuery, window, document);
378 + const extractAllowedCSSPropertiesFromFontFields = function (subfields) {
379 + // Nothing is allowed by default.
380 + const allowedProperties = {
381 + 'font-family': false,
382 + 'font-weight': false,
383 + 'font-style': false,
384 + 'font-size': false,
385 + 'line-height': false,
386 + 'letter-spacing': false,
387 + 'text-align': false,
388 + 'text-transform': false,
389 + 'text-decoration': false,
390 + }
391 +
392 + if (_.isEmpty(subfields)) {
393 + return allowedProperties
394 + }
395 +
396 + // We will match the subfield keys with the CSS properties, but only those that properties that are allowed.
397 + // Maybe at some point some more complex matching would be needed here.
398 + _.each(subfields, function (value, key) {
399 + if (typeof allowedProperties[key] !== 'undefined') {
400 + // Convert values to boolean.
401 + allowedProperties[key] = !!value
402 +
403 + // For font-weight we want font-style to go the same way,
404 + // since these two are generated from the same subfield: font-weight (actually holding the font variant value).
405 + if ('font-weight' === key) {
406 + allowedProperties['font-style'] = allowedProperties[key]
407 + }
408 + }
409 + })
410 +
411 + return allowedProperties
412 + }
413 +
414 + // This is a mirror logic of the server-side Customify_Fonts_Global::getSubFieldUnit()
415 + const getFieldUnit = function (settingID, field) {
416 + if (typeof customify.config.settings[settingID] === 'undefined' || typeof customify.config.settings[settingID].fields[field] === 'undefined') {
417 + // These fields don't have an unit, by default.
418 + if (_.includes(['font-family', 'font-weight', 'font-style', 'line-height', 'text-align', 'text-transform', 'text-decoration'], field)) {
419 + return false
420 + }
421 +
422 + // The rest of the subfields have pixels as default units.
423 + return 'px'
424 + }
425 +
426 + if (typeof customify.config.settings[settingID].fields[field].unit !== 'undefined') {
427 + // Make sure that we convert all falsy unit values to the boolean false.
428 + return _.includes(['', 'false', false], customify.config.settings[settingID].fields[field].unit) ? false : customify.config.settings[settingID].fields[field].unit
429 + }
430 +
431 + if (typeof customify.config.settings[settingID].fields[field][3] !== 'undefined') {
432 + // Make sure that we convert all falsy unit values to the boolean false.
433 + return _.includes(['', 'false', false], customify.config.settings[settingID].fields[field][3]) ? false : customify.config.settings[settingID].fields[field][3]
434 + }
435 +
436 + return 'px'
437 + }
438 +
439 + const maybeLoadFontFamily = function (font, settingID) {
440 + if (typeof font.font_family === 'undefined') {
441 + return
442 + }
443 +
444 + const fontConfig = customify.config.settings[settingID]
445 +
446 + let family = font.font_family
447 + // The font family may be a comma separated list like "Roboto, sans"
448 + const fontType = customify.fontFields.determineFontType(family)
449 +
450 + if ('system_font' === fontType) {
451 + // Nothing to do for standard fonts
452 + return
453 + }
454 +
455 + const fontDetails = customify.fontFields.getFontDetails(family, fontType)
456 +
457 + // Handle theme defined fonts and cloud fonts together since they are very similar.
458 + if (fontType === 'theme_font' || fontType === 'cloud_font') {
459 +
460 + // Bail if we have no src.
461 + if (typeof fontDetails.src === undefined) {
462 + return
463 + }
464 +
465 + // Handle the font variants.
466 + // If there is a selected font variant and we haven't been instructed to load all, load only that,
467 + // otherwise load all the available variants.
468 + let variants =
469 + (
470 + typeof font.font_variant !== 'undefined'
471 + && (typeof fontConfig['fields']['font-weight']['loadAllVariants'] === 'undefined' || !fontConfig['fields']['font-weight']['loadAllVariants'])
472 + ) ? font.font_variant : typeof fontDetails.variants !== 'undefined' ? fontDetails.variants : []
473 +
474 + if (!_.isEmpty(variants)) {
475 + variants = standardizeToArray(variants)
476 +
477 + if (!_.isEmpty(variants)) {
478 + family = family + ':' + variants.map(function (variant) {
479 + return customify.fontFields.convertFontVariantToFVD(variant)
480 + }).join(',')
481 + }
482 + }
483 +
484 + if (fontsCache.indexOf(family) === -1) {
485 + WebFont.load({
486 + custom: {
487 + families: [family],
488 + urls: [fontDetails.src]
489 + },
490 + classes: false,
491 + events: false,
492 + })
493 +
494 + // Remember we've loaded this family (with it's variants) so we don't load it again.
495 + fontsCache.push(family)
496 + }
497 + }
498 + // Handle Google fonts since Web Font Loader has a special module for them.
499 + else if (fontType === 'google_font') {
500 +
501 + // Handle the font variants
502 + // If there is a selected font variant and we haven't been instructed to load all, load only that,
503 + // otherwise load all the available variants.
504 + let variants =
505 + (
506 + typeof font.font_variant !== 'undefined'
507 + && (typeof fontConfig['fields']['font-weight']['loadAllVariants'] === 'undefined' || !fontConfig['fields']['font-weight']['loadAllVariants'])
508 + ) ? font.font_variant : typeof fontDetails.variants !== 'undefined' ? fontDetails.variants : []
509 +
510 + if (!_.isEmpty(variants)) {
511 + variants = standardizeToArray(variants)
512 +
513 + if (!_.isEmpty(variants)) {
514 + family = family + ':' + variants.join(',')
515 + }
516 + }
517 +
518 + if (fontsCache.indexOf(family) === -1) {
519 + WebFont.load({
520 + google: {families: [family]},
521 + classes: false,
522 + events: false,
523 + })
524 +
525 + // Remember we've loaded this family (with it's variants) so we don't load it again.
526 + fontsCache.push(family)
527 + }
528 +
529 + } else {
530 + // Maybe Typekit, Fonts.com or Fontdeck fonts
531 + }
532 + }
533 +
534 + const standardizeToArray = function (value) {
535 + if (typeof value === 'string' || typeof value === 'number') {
536 + value = [value]
537 + } else if (typeof value === 'object') {
538 + value = Object.values(value)
539 + }
540 +
541 + return value
542 + }
543 +
544 + // This is a mirror logic of the server-side Customify_Fonts_Global::getFontFamilyFallbackStack()
545 + const getFontFamilyFallbackStack = function (fontFamily) {
546 + let fallbackStack = ''
547 +
548 + const fontDetails = customify.fontFields.getFontDetails(fontFamily)
549 + if (typeof fontDetails.fallback_stack !== 'undefined' && !_.isEmpty(fontDetails.fallback_stack)) {
550 + fallbackStack = fontDetails.fallback_stack
551 + } else if (typeof fontDetails.category !== 'undefined' && !_.isEmpty(fontDetails.category)) {
552 + const category = fontDetails.category
553 + // Search in the available categories for a match.
554 + if (typeof customify.fonts.categories[category] !== 'undefined') {
555 + // Matched by category ID/key
556 + fallbackStack = typeof customify.fonts.categories[category].fallback_stack !== 'undefined' ? customify.fonts.categories[category].fallback_stack : ''
557 + } else {
558 + // We need to search for aliases.
559 + _.find(customify.fonts.categories, function (categoryDetails) {
560 + if (typeof categoryDetails.aliases !== 'undefined') {
561 + const aliases = maybeImplodeList(categoryDetails.aliases)
562 + if (aliases.indexOf(category) !== -1) {
563 + // Found it.
564 + fallbackStack = typeof categoryDetails.fallback_stack !== 'undefined' ? categoryDetails.fallback_stack : ''
565 + return true
566 + }
567 + }
568 +
569 + return false
570 + })
571 + }
572 + }
573 +
574 + return fallbackStack
575 + }
576 +
577 + // Mirror logic of server-side Customify_Fonts_Global::sanitizeFontFamilyCSSValue()
578 + const sanitizeFontFamilyCSSValue = function (value) {
579 + // Since we might get a stack, attempt to treat is a comma-delimited list.
580 + let fontFamilies = maybeExplodeList(value)
581 + if (!fontFamilies.length) {
582 + return ''
583 + }
584 +
585 + _.each(fontFamilies, function (fontFamily, key) {
586 + // Make sure that the font family is free from " or ' or whitespace, at the front.
587 + fontFamily = fontFamily.replace(new RegExp(/^\s*["'‘’“”]*\s*/), '')
588 + // Make sure that the font family is free from " or ' or whitespace, at the back.
589 + fontFamily = fontFamily.replace(new RegExp(/\s*["'‘’“”]*\s*$/), '')
590 +
591 + if ('' === fontFamily) {
592 + delete fontFamilies[key]
593 + return;
594 + }
595 +
596 + // Now, if the font family contains spaces, wrap it in ".
597 + if (fontFamily.indexOf(' ') !== -1) {
598 + fontFamily = '"' + fontFamily + '"'
599 + }
600 +
601 + // Finally, put it back.
602 + fontFamilies[key] = fontFamily
603 + })
604 +
605 + return maybeImplodeList( fontFamilies );
606 + }
607 +
608 + const maybeExplodeList = function(str, delimiter = ',') {
609 + if (typeof str === 'object') {
610 + str = standardizeToArray(str)
611 + }
612 +
613 + // If by any chance we are given an array, just return it
614 + if (Array.isArray(str)) {
615 + return str
616 + }
617 +
618 + // Anything else we coerce to a string
619 + if ( typeof str !== 'string' ) {
620 + str = String(str);
621 + }
622 +
623 + // Make sure we trim it
624 + str = str.trim();
625 +
626 + // Bail on empty string
627 + if ( !str.length ) {
628 + return [];
629 + }
630 +
631 + // Return the whole string as an element if the delimiter is missing
632 + if ( str.indexOf(delimiter) === -1 ) {
633 + return [str];
634 + }
635 +
636 + // Explode it and return it
637 + return explode(delimiter, str);
638 + }
639 +
640 + const maybeImplodeList = function(value, glue = ',') {
641 + // If by any chance we are given a string, just return it
642 + if (typeof value === 'string' || typeof value === 'number') {
643 + return String(value)
644 + }
645 +
646 + if (typeof value === 'object') {
647 + value = standardizeToArray(value)
648 + }
649 +
650 + if (Array.isArray(value)) {
651 + return implode(glue, value)
652 + }
653 +
654 + // For anything else we return an empty string.
655 + return ''
656 + }
657 +
658 + const explode = function (delimiter, string, limit) {
659 + // discuss at: https://locutus.io/php/explode/
660 + // original by: Kevin van Zonneveld (https://kvz.io)
661 + // example 1: explode(' ', 'Kevin van Zonneveld')
662 + // returns 1: [ 'Kevin', 'van', 'Zonneveld' ]
663 +
664 + if (arguments.length < 2 ||
665 + typeof delimiter === 'undefined' ||
666 + typeof string === 'undefined') {
667 + return null
668 + }
669 + if (delimiter === '' ||
670 + delimiter === false ||
671 + delimiter === null) {
672 + return false
673 + }
674 + if (typeof delimiter === 'function' ||
675 + typeof delimiter === 'object' ||
676 + typeof string === 'function' ||
677 + typeof string === 'object') {
678 + return {
679 + 0: ''
680 + }
681 + }
682 + if (delimiter === true) {
683 + delimiter = '1'
684 + }
685 +
686 + // Here we go...
687 + delimiter += ''
688 + string += ''
689 +
690 + let s = string.split(delimiter)
691 +
692 + if (typeof limit === 'undefined') return s
693 +
694 + // Support for limit
695 + if (limit === 0) limit = 1
696 +
697 + // Positive limit
698 + if (limit > 0) {
699 + if (limit >= s.length) {
700 + return s
701 + }
702 + return s
703 + .slice(0, limit - 1)
704 + .concat([s.slice(limit - 1)
705 + .join(delimiter)
706 + ])
707 + }
708 +
709 + // Negative limit
710 + if (-limit >= s.length) {
711 + return []
712 + }
713 +
714 + s.splice(s.length + limit)
715 + return s
716 + }
717 +
718 + const implode = function (glue, pieces) {
719 + // discuss at: https://locutus.io/php/implode/
720 + // original by: Kevin van Zonneveld (https://kvz.io)
721 + // improved by: Waldo Malqui Silva (https://waldo.malqui.info)
722 + // improved by: Itsacon (https://www.itsacon.net/)
723 + // bugfixed by: Brett Zamir (https://brett-zamir.me)
724 + // example 1: implode(' ', ['Kevin', 'van', 'Zonneveld'])
725 + // returns 1: 'Kevin van Zonneveld'
726 + // example 2: implode(' ', {first:'Kevin', last: 'van Zonneveld'})
727 + // returns 2: 'Kevin van Zonneveld'
728 +
729 + let i = ''
730 + let retVal = ''
731 + let tGlue = ''
732 +
733 + if (arguments.length === 1) {
734 + pieces = glue
735 + glue = ''
736 + }
737 +
738 + if (typeof pieces === 'object') {
739 + if (Object.prototype.toString.call(pieces) === '[object Array]') {
740 + return pieces.join(glue)
741 + }
742 + for (i in pieces) {
743 + retVal += tGlue + pieces[i]
744 + tGlue = glue
745 + }
746 + return retVal
747 + }
748 +
749 + return pieces
750 + }
751 + })
752 +})(jQuery, window, document)