Policy 1707 commit to LF
[policy/engine.git] / POLICY-SDK-APP / src / main / webapp / app / policyApp / CSS / bootstrap / docs / assets / js / src / customizer.js
1 /*!
2  * Bootstrap Customizer (http://getbootstrap.com/customize/)
3  * Copyright 2011-2014 Twitter, Inc.
4  *
5  * Licensed under the Creative Commons Attribution 3.0 Unported License. For
6  * details, see http://creativecommons.org/licenses/by/3.0/.
7  */
8
9 /* jshint es3:false */
10 /* global JSZip, less, autoprefixer, saveAs, UglifyJS, __configBridge, __js, __less, __fonts */
11
12 window.onload = function () { // wait for load in a dumb way because B-0
13   'use strict';
14   var cw = '/*!\n' +
15            ' * Bootstrap v3.3.4 (http://getbootstrap.com)\n' +
16            ' * Copyright 2011-' + new Date().getFullYear() + ' Twitter, Inc.\n' +
17            ' * Licensed under MIT (https://github.com/twbs/bootstrap/blob/master/LICENSE)\n' +
18            ' */\n\n'
19
20   var supportsFile = (window.File && window.FileReader && window.FileList && window.Blob)
21   var importDropTarget = $('#import-drop-target')
22
23   function showError(msg, err) {
24     $('<div id="bsCustomizerAlert" class="bs-customizer-alert">' +
25         '<div class="container">' +
26           '<a href="#bsCustomizerAlert" data-dismiss="alert" class="close pull-right" aria-label="Close" role="button"><span aria-hidden="true">&times;</span></a>' +
27           '<p class="bs-customizer-alert-text"><span class="glyphicon glyphicon-warning-sign" aria-hidden="true"></span><span class="sr-only">Warning:</span>' + msg + '</p>' +
28           (err.message ? $('<p></p>').text('Error: ' + err.message)[0].outerHTML : '') +
29           (err.extract ? $('<pre class="bs-customizer-alert-extract"></pre>').text(err.extract.join('\n'))[0].outerHTML : '') +
30         '</div>' +
31       '</div>').appendTo('body').alert()
32     throw err
33   }
34
35   function showSuccess(msg) {
36     $('<div class="bs-callout bs-callout-info">' +
37       '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button>' + msg +
38     '</div>').insertAfter('.bs-customize-download')
39   }
40
41   function showCallout(msg, showUpTop) {
42     var callout = $('<div class="bs-callout bs-callout-danger">' +
43       '<h4>Attention!</h4>' +
44       '<p>' + msg + '</p>' +
45     '</div>')
46
47     if (showUpTop) {
48       callout.appendTo('.bs-docs-container')
49     } else {
50       callout.insertAfter('.bs-customize-download')
51     }
52   }
53
54   function showAlert(type, msg, insertAfter) {
55     $('<div class="alert alert-' + type + '">' + msg + '<button type="button" class="close" data-dismiss="alert" aria-label="Close"><span aria-hidden="true">&times;</span></button></div>')
56       .insertAfter(insertAfter)
57   }
58
59   function getQueryParam(key) {
60     key = key.replace(/[*+?^$.\[\]{}()|\\\/]/g, '\\$&') // escape RegEx meta chars
61     var match = location.search.match(new RegExp('[?&]' + key + '=([^&]+)(&|$)'))
62     return match && decodeURIComponent(match[1].replace(/\+/g, ' '))
63   }
64
65   function createGist(configJson, callback) {
66     var data = {
67       description: 'Bootstrap Customizer Config',
68       'public': true,
69       files: {
70         'config.json': {
71           content: configJson
72         }
73       }
74     }
75     $.ajax({
76       url: 'https://api.github.com/gists',
77       type: 'POST',
78       contentType: 'application/json; charset=UTF-8',
79       dataType: 'json',
80       data: JSON.stringify(data)
81     })
82     .success(function (result) {
83       var gistUrl = result.html_url;
84       var origin = window.location.protocol + '//' + window.location.host
85       var customizerUrl = origin + window.location.pathname + '?id=' + result.id
86       showSuccess('<strong>Success!</strong> Your configuration has been saved to <a href="' + gistUrl + '">' + gistUrl + '</a> ' +
87         'and can be revisited here at <a href="' + customizerUrl + '">' + customizerUrl + '</a> for further customization.')
88       history.replaceState(false, document.title, customizerUrl)
89       callback(gistUrl, customizerUrl)
90     })
91     .error(function (err) {
92       try {
93         showError('<strong>Ruh roh!</strong> Could not save gist file, configuration not saved.', err)
94       } catch (sameErr) {
95         // deliberately ignore the error
96       }
97       callback('<none>', '<none>')
98     })
99   }
100
101   function getCustomizerData() {
102     var vars = {}
103
104     $('#less-variables-section input')
105       .each(function () {
106         $(this).val() && (vars[$(this).prev().text()] = $(this).val())
107       })
108
109     var data = {
110       vars: vars,
111       css: $('#less-section input:checked')  .map(function () { return this.value }).toArray(),
112       js:  $('#plugin-section input:checked').map(function () { return this.value }).toArray()
113     }
114
115     if ($.isEmptyObject(data.vars) && !data.css.length && !data.js.length) return
116
117     return data
118   }
119
120   function updateCustomizerFromJson(data) {
121     if (data.js) {
122       $('#plugin-section input').each(function () {
123         $(this).prop('checked', ~$.inArray(this.value, data.js))
124       })
125     }
126     if (data.css) {
127       $('#less-section input').each(function () {
128         $(this).prop('checked', ~$.inArray(this.value, data.css))
129       })
130     }
131     if (data.vars) {
132       for (var i in data.vars) {
133         $('input[data-var="' + i + '"]').val(data.vars[i])
134       }
135     }
136   }
137
138   function parseUrl() {
139     var id = getQueryParam('id')
140
141     if (!id) return
142
143     $.ajax({
144       url: 'https://api.github.com/gists/' + id,
145       type: 'GET',
146       dataType: 'json'
147     })
148     .success(function (result) {
149       var data = JSON.parse(result.files['config.json'].content)
150       updateCustomizerFromJson(data)
151     })
152     .error(function (err) {
153       showError('Error fetching bootstrap config file', err)
154     })
155   }
156
157   function generateZip(css, js, fonts, config, complete) {
158     if (!css && !js) return showError('<strong>Ruh roh!</strong> No Bootstrap files selected.', new Error('no Bootstrap'))
159
160     var zip = new JSZip()
161
162     if (css) {
163       var cssFolder = zip.folder('css')
164       for (var fileName in css) {
165         cssFolder.file(fileName, css[fileName])
166       }
167     }
168
169     if (js) {
170       var jsFolder = zip.folder('js')
171       for (var jsFileName in js) {
172         jsFolder.file(jsFileName, js[jsFileName])
173       }
174     }
175
176     if (fonts) {
177       var fontsFolder = zip.folder('fonts')
178       for (var fontsFileName in fonts) {
179         fontsFolder.file(fontsFileName, fonts[fontsFileName], { base64: true })
180       }
181     }
182
183     if (config) {
184       zip.file('config.json', config)
185     }
186
187     var content = zip.generate({ type: 'blob' })
188
189     complete(content)
190   }
191
192   function generateCustomLess(vars) {
193     var result = ''
194
195     for (var key in vars) {
196       result += key + ': ' + vars[key] + ';\n'
197     }
198
199     return result + '\n\n'
200   }
201
202   function generateFonts() {
203     var glyphicons = $('#less-section [value="glyphicons.less"]:checked')
204     if (glyphicons.length) {
205       return __fonts
206     }
207   }
208
209   // Returns an Array of @import'd filenames in the order
210   // in which they appear in the file.
211   function includedLessFilenames(lessFilename) {
212     var IMPORT_REGEX = /^@import \"(.*?)\";$/
213     var lessLines = __less[lessFilename].split('\n')
214
215     var imports = []
216     $.each(lessLines, function (index, lessLine) {
217       var match = IMPORT_REGEX.exec(lessLine)
218       if (match) {
219         var importee = match[1]
220         var transitiveImports = includedLessFilenames(importee)
221         $.each(transitiveImports, function (index, transitiveImportee) {
222           if ($.inArray(transitiveImportee, imports) === -1) {
223             imports.push(transitiveImportee)
224           }
225         })
226         imports.push(importee)
227       }
228     })
229
230     return imports
231   }
232
233   function generateLESS(lessFilename, lessFileIncludes, vars) {
234     var lessSource = __less[lessFilename]
235
236     var lessFilenames = includedLessFilenames(lessFilename)
237     $.each(lessFilenames, function (index, filename) {
238       var fileInclude = lessFileIncludes[filename]
239
240       // Files not explicitly unchecked are compiled into the final stylesheet.
241       // Core stylesheets like 'normalize.less' are not included in the form
242       // since disabling them would wreck everything, and so their 'fileInclude'
243       // will be 'undefined'.
244       if (fileInclude || (fileInclude == null))    lessSource += __less[filename]
245
246       // Custom variables are added after Bootstrap variables so the custom
247       // ones take precedence.
248       if (('variables.less' === filename) && vars) lessSource += generateCustomLess(vars)
249     })
250
251     lessSource = lessSource.replace(/@import[^\n]*/gi, '') // strip any imports
252     return lessSource
253   }
254
255   function compileLESS(lessSource, baseFilename, intoResult) {
256     var promise = $.Deferred()
257     var parser = new less.Parser({
258       paths: ['variables.less', 'mixins.less'],
259       optimization: 0,
260       filename: baseFilename + '.css'
261     })
262
263     parser.parse(lessSource, function (parseErr, tree) {
264       if (parseErr) {
265         return promise.reject(parseErr)
266       }
267       try {
268         intoResult[baseFilename + '.css']     = cw + tree.toCSS()
269         intoResult[baseFilename + '.min.css'] = cw + tree.toCSS({ compress: true })
270       }
271       catch (compileErr) {
272         return promise.reject(compileErr)
273       }
274       promise.resolve()
275     })
276
277     return promise.promise()
278   }
279
280   function generateCSS(preamble) {
281     var promise = $.Deferred()
282     var oneChecked = false
283     var lessFileIncludes = {}
284     $('#less-section input').each(function () {
285       var $this = $(this)
286       var checked = $this.is(':checked')
287       lessFileIncludes[$this.val()] = checked
288
289       oneChecked = oneChecked || checked
290     })
291
292     if (!oneChecked) return false
293
294     var result = {}
295     var vars = {}
296
297     $('#less-variables-section input')
298       .each(function () {
299         $(this).val() && (vars[$(this).prev().text()] = $(this).val())
300       })
301
302     var bsLessSource    = preamble + generateLESS('bootstrap.less', lessFileIncludes, vars)
303     var themeLessSource = preamble + generateLESS('theme.less',     lessFileIncludes, vars)
304
305     var prefixer = autoprefixer({ browsers: __configBridge.autoprefixerBrowsers })
306
307     $.when(
308       compileLESS(bsLessSource, 'bootstrap', result),
309       compileLESS(themeLessSource, 'bootstrap-theme', result)
310     ).done(function () {
311       for (var key in result) {
312         result[key] = prefixer.process(result[key]).css
313       }
314       promise.resolve(result)
315     }).fail(function (err) {
316       showError('<strong>Ruh roh!</strong> Problem parsing or compiling Less files.', err)
317       promise.reject()
318     })
319
320     return promise.promise()
321   }
322
323   function uglify(js) {
324     var ast = UglifyJS.parse(js)
325     ast.figure_out_scope()
326
327     var compressor = UglifyJS.Compressor()
328     var compressedAst = ast.transform(compressor)
329
330     compressedAst.figure_out_scope()
331     compressedAst.compute_char_frequency()
332     compressedAst.mangle_names()
333
334     var stream = UglifyJS.OutputStream()
335     compressedAst.print(stream)
336
337     return stream.toString()
338   }
339
340   function generateJS(preamble) {
341     var $checked = $('#plugin-section input:checked')
342     var jqueryCheck = __configBridge.jqueryCheck.join('\n')
343     var jqueryVersionCheck = __configBridge.jqueryVersionCheck.join('\n')
344
345     if (!$checked.length) return false
346
347     var js = $checked
348       .map(function () { return __js[this.value] })
349       .toArray()
350       .join('\n')
351
352     preamble = cw + preamble
353     js = jqueryCheck + jqueryVersionCheck + js
354
355     return {
356       'bootstrap.js': preamble + js,
357       'bootstrap.min.js': preamble + uglify(js)
358     }
359   }
360
361   function removeImportAlerts() {
362     importDropTarget.nextAll('.alert').remove()
363   }
364
365   function handleConfigFileSelect(e) {
366     e.stopPropagation()
367     e.preventDefault()
368
369     var file = (e.originalEvent.hasOwnProperty('dataTransfer')) ? e.originalEvent.dataTransfer.files[0] : e.originalEvent.target.files[0]
370
371     var reader = new FileReader()
372
373     reader.onload = function (e) {
374       var text = e.target.result
375
376       try {
377         var json = JSON.parse(text)
378
379         if (!$.isPlainObject(json)) {
380           throw new Error('JSON data from config file is not an object.')
381         }
382
383         updateCustomizerFromJson(json)
384         showAlert('success', '<strong>Woohoo!</strong> Your configuration was successfully uploaded. Tweak your settings, then hit Download.', importDropTarget)
385       } catch (err) {
386         return showAlert('danger', '<strong>Shucks.</strong> We can only read valid <code>.json</code> files. Please try again.', importDropTarget)
387       }
388     }
389
390     reader.readAsText(file, 'utf-8')
391   }
392
393   function handleConfigDragOver(e) {
394     e.stopPropagation()
395     e.preventDefault()
396     e.originalEvent.dataTransfer.dropEffect = 'copy'
397
398     removeImportAlerts()
399   }
400
401   if (supportsFile) {
402     importDropTarget
403       .on('dragover', handleConfigDragOver)
404       .on('drop', handleConfigFileSelect)
405   }
406
407   $('#import-file-select').on('change', handleConfigFileSelect)
408   $('#import-manual-trigger').on('click', removeImportAlerts)
409
410   var inputsComponent = $('#less-section input')
411   var inputsPlugin    = $('#plugin-section input')
412   var inputsVariables = $('#less-variables-section input')
413
414   $('#less-section .toggle').on('click', function (e) {
415     e.preventDefault()
416     inputsComponent.prop('checked', !inputsComponent.is(':checked'))
417   })
418
419   $('#plugin-section .toggle').on('click', function (e) {
420     e.preventDefault()
421     inputsPlugin.prop('checked', !inputsPlugin.is(':checked'))
422   })
423
424   $('#less-variables-section .toggle').on('click', function (e) {
425     e.preventDefault()
426     inputsVariables.val('')
427   })
428
429   $('[data-dependencies]').on('click', function () {
430     if (!$(this).is(':checked')) return
431     var dependencies = this.getAttribute('data-dependencies')
432     if (!dependencies) return
433     dependencies = dependencies.split(',')
434     for (var i = 0; i < dependencies.length; i++) {
435       var dependency = $('[value="' + dependencies[i] + '"]')
436       dependency && dependency.prop('checked', true)
437     }
438   })
439
440   $('[data-dependents]').on('click', function () {
441     if ($(this).is(':checked')) return
442     var dependents = this.getAttribute('data-dependents')
443     if (!dependents) return
444     dependents = dependents.split(',')
445     for (var i = 0; i < dependents.length; i++) {
446       var dependent = $('[value="' + dependents[i] + '"]')
447       dependent && dependent.prop('checked', false)
448     }
449   })
450
451   var $compileBtn = $('#btn-compile')
452
453   $compileBtn.on('click', function (e) {
454     var configData = getCustomizerData()
455     var configJson = JSON.stringify(configData, null, 2)
456
457     e.preventDefault()
458
459     $compileBtn.attr('disabled', 'disabled')
460
461     createGist(configJson, function (gistUrl, customizerUrl) {
462       configData.customizerUrl = customizerUrl
463       configJson = JSON.stringify(configData, null, 2)
464
465       var preamble = '/*!\n' +
466         ' * Generated using the Bootstrap Customizer (' + customizerUrl + ')\n' +
467         ' * Config saved to config.json and ' + gistUrl + '\n' +
468         ' */\n'
469
470       $.when(
471         generateCSS(preamble),
472         generateJS(preamble),
473         generateFonts()
474       ).done(function (css, js, fonts) {
475         generateZip(css, js, fonts, configJson, function (blob) {
476           $compileBtn.removeAttr('disabled')
477           setTimeout(function () { saveAs(blob, 'bootstrap.zip') }, 0)
478         })
479       })
480     })
481   });
482
483   // browser support alert
484   (function () {
485     function failback() {
486       $('.bs-docs-section, .bs-docs-sidebar').css('display', 'none')
487       showCallout('Looks like your current browser doesn\'t support the Bootstrap Customizer. Please take a second ' +
488                     'to <a href="http://browsehappy.com/">upgrade to a more modern browser</a> (other than Safari).', true)
489     }
490     /**
491      * Based on:
492      *   Blob Feature Check v1.1.0
493      *   https://github.com/ssorallen/blob-feature-check/
494      *   License: Public domain (http://unlicense.org)
495      */
496     var url = window.webkitURL || window.URL // Safari 6 uses "webkitURL".
497     var svg = new Blob(
498       ['<svg xmlns=\'http://www.w3.org/2000/svg\'></svg>'],
499       { type: 'image/svg+xml;charset=utf-8' }
500     )
501     var objectUrl = url.createObjectURL(svg);
502
503     if (/^blob:/.exec(objectUrl) === null || !supportsFile) {
504       // `URL.createObjectURL` created a URL that started with something other
505       // than "blob:", which means it has been polyfilled and is not supported by
506       // this browser.
507       failback()
508     } else {
509       $('<img>')
510         .on('load', function () {
511           $compileBtn.prop('disabled', false)
512         })
513         .on('error', failback)
514         .attr('src', objectUrl)
515     }
516   })();
517
518   parseUrl()
519 }