2013-10-17 20:33:04 +02:00
/ * !
* Copyright 2013 Twitter , Inc .
2013-10-17 23:11:40 +02:00
*
* Licensed under the Creative Commons Attribution 3.0 Unported License . For
* details , see http : //creativecommons.org/licenses/by/3.0/.
2013-10-17 20:33:04 +02:00
* /
2013-09-18 20:37:55 +02:00
/* jshint multistr:true */
2013-10-17 20:33:04 +02:00
2013-08-12 03:42:13 +02:00
window . onload = function ( ) { // wait for load in a dumb way because B-0
2013-12-02 04:31:52 +01:00
var cw = '/*!\n * Bootstrap v3.0.3\n *\n * Copyright 2013 Twitter, Inc\n * Licensed under the Apache License v2.0\n * http://www.apache.org/licenses/LICENSE-2.0\n *\n * Designed and built with all the love in the world @twitter by @mdo and @fat.\n */\n\n'
2013-08-12 03:42:13 +02:00
2013-08-18 09:28:52 +02:00
function showError ( msg , err ) {
2013-08-18 04:04:41 +02:00
$ ( ' < div id = "bsCustomizerAlert" class = "bs-customizer-alert" > \
< div class = "container" > \
< a href = "#bsCustomizerAlert" data - dismiss = "alert" class = "close pull-right" > & times ; < / a > \
2013-08-19 09:54:53 +02:00
< p class = "bs-customizer-alert-text" > < span class = "glyphicon glyphicon-warning-sign" > < / s p a n > ' + m s g + ' < / p > ' +
2013-08-18 04:04:41 +02:00
( err . extract ? '<pre class="bs-customizer-alert-extract">' + err . extract . join ( '\n' ) + '</pre>' : '' ) + ' \
< / d i v > \
< / d i v > ' ) . a p p e n d T o ( ' b o d y ' ) . a l e r t ( )
throw err
}
2013-08-18 09:28:52 +02:00
function showCallout ( msg , showUpTop ) {
var callout = $ ( ' < div class = "bs-callout bs-callout-danger" > \
< h4 > Attention ! < / h 4 > \
< p > ' + msg + ' < / p > \
< / d i v > ' )
if ( showUpTop ) {
callout . appendTo ( '.bs-docs-container' )
} else {
callout . insertAfter ( '.bs-customize-download' )
}
}
2013-08-18 03:41:36 +02:00
function getQueryParam ( key ) {
2013-09-18 18:50:02 +02:00
key = key . replace ( /[*+?^$.\[\]{}()|\\\/]/g , '\\$&' ) ; // escape RegEx meta chars
var match = location . search . match ( new RegExp ( '[?&]' + key + '=([^&]+)(&|$)' ) ) ;
return match && decodeURIComponent ( match [ 1 ] . replace ( /\+/g , ' ' ) ) ;
2013-08-18 03:41:36 +02:00
}
2013-09-14 10:26:42 +02:00
function createGist ( configJson ) {
2013-08-18 03:41:36 +02:00
var data = {
2013-09-18 18:50:02 +02:00
'description' : 'Bootstrap Customizer Config' ,
'public' : true ,
'files' : {
'config.json' : {
'content' : configJson
2013-08-18 03:41:36 +02:00
}
}
}
$ . ajax ( {
url : 'https://api.github.com/gists' ,
type : 'POST' ,
dataType : 'json' ,
data : JSON . stringify ( data )
} )
2013-09-18 20:37:55 +02:00
. success ( function ( result ) {
2013-09-18 18:50:02 +02:00
var origin = window . location . protocol + '//' + window . location . host
2013-11-02 02:56:27 +01:00
history . replaceState ( false , document . title , origin + window . location . pathname + '?id=' + result . id )
2013-08-18 03:41:36 +02:00
} )
2013-09-18 20:37:55 +02:00
. error ( function ( err ) {
2013-08-19 08:36:36 +02:00
showError ( '<strong>Ruh roh!</strong> Could not save gist file, configuration not saved.' , err )
2013-08-18 03:41:36 +02:00
} )
}
2013-08-18 09:28:52 +02:00
function getCustomizerData ( ) {
2013-08-12 03:42:13 +02:00
var vars = { }
$ ( '#less-variables-section input' )
. each ( function ( ) {
2013-09-18 20:37:55 +02:00
$ ( this ) . val ( ) && ( vars [ $ ( this ) . prev ( ) . text ( ) ] = $ ( this ) . val ( ) )
2013-08-12 03:42:13 +02:00
} )
var data = {
vars : vars ,
2013-08-18 03:41:36 +02:00
css : $ ( '#less-section input:checked' ) . map ( function ( ) { return this . value } ) . toArray ( ) ,
js : $ ( '#plugin-section input:checked' ) . map ( function ( ) { return this . value } ) . toArray ( )
2013-08-12 03:42:13 +02:00
}
if ( $ . isEmptyObject ( data . vars ) && ! data . css . length && ! data . js . length ) return
2013-08-18 09:28:52 +02:00
return data
2013-08-12 03:42:13 +02:00
}
function parseUrl ( ) {
2013-08-18 03:41:36 +02:00
var id = getQueryParam ( 'id' )
2013-08-12 03:42:13 +02:00
2013-08-18 03:41:36 +02:00
if ( ! id ) return
2013-08-12 03:42:13 +02:00
2013-08-18 03:41:36 +02:00
$ . ajax ( {
url : 'https://api.github.com/gists/' + id ,
type : 'GET' ,
dataType : 'json'
} )
2013-09-18 20:37:55 +02:00
. success ( function ( result ) {
2013-08-18 03:41:36 +02:00
var data = JSON . parse ( result . files [ 'config.json' ] . content )
if ( data . js ) {
$ ( '#plugin-section input' ) . each ( function ( ) {
$ ( this ) . prop ( 'checked' , ~ $ . inArray ( this . value , data . js ) )
} )
2013-08-12 03:42:13 +02:00
}
2013-08-18 03:41:36 +02:00
if ( data . css ) {
$ ( '#less-section input' ) . each ( function ( ) {
$ ( this ) . prop ( 'checked' , ~ $ . inArray ( this . value , data . css ) )
} )
}
if ( data . vars ) {
for ( var i in data . vars ) {
$ ( 'input[data-var="' + i + '"]' ) . val ( data . vars [ i ] )
}
}
} )
2013-09-18 20:37:55 +02:00
. error ( function ( err ) {
2013-08-18 04:04:41 +02:00
showError ( 'Error fetching bootstrap config file' , err )
2013-08-18 03:41:36 +02:00
} )
2013-08-12 03:42:13 +02:00
}
2013-09-14 10:26:42 +02:00
function generateZip ( css , js , fonts , config , complete ) {
2013-08-19 08:36:36 +02:00
if ( ! css && ! js ) return showError ( '<strong>Ruh roh!</strong> No Bootstrap files selected.' , new Error ( 'no Bootstrap' ) )
2013-08-12 03:42:13 +02:00
var zip = new JSZip ( )
if ( css ) {
var cssFolder = zip . folder ( 'css' )
for ( var fileName in css ) {
cssFolder . file ( fileName , css [ fileName ] )
}
}
if ( js ) {
var jsFolder = zip . folder ( 'js' )
2013-09-18 20:37:55 +02:00
for ( var jsFileName in js ) {
jsFolder . file ( jsFileName , js [ jsFileName ] )
2013-08-12 03:42:13 +02:00
}
}
2013-08-19 21:19:00 +02:00
if ( fonts ) {
var fontsFolder = zip . folder ( 'fonts' )
2013-09-18 20:37:55 +02:00
for ( var fontsFileName in fonts ) {
fontsFolder . file ( fontsFileName , fonts [ fontsFileName ] , { base64 : true } )
2013-08-19 21:19:00 +02:00
}
}
2013-09-14 10:26:42 +02:00
if ( config ) {
2013-09-18 18:50:02 +02:00
zip . file ( 'config.json' , config )
2013-09-14 10:26:42 +02:00
}
2013-09-18 18:50:02 +02:00
var content = zip . generate ( { type : 'blob' } )
2013-08-18 09:28:52 +02:00
complete ( content )
2013-08-12 03:42:13 +02:00
}
function generateCustomCSS ( vars ) {
var result = ''
for ( var key in vars ) {
result += key + ': ' + vars [ key ] + ';\n'
}
return result + '\n\n'
}
2013-08-19 21:19:00 +02:00
function generateFonts ( ) {
var glyphicons = $ ( '#less-section [value="glyphicons.less"]:checked' )
if ( glyphicons . length ) {
return _ _fonts
}
}
2013-09-24 08:29:45 +02:00
// Returns an Array of @import'd filenames from 'bootstrap.less' in the order
// in which they appear in the file.
function bootstrapLessFilenames ( ) {
var IMPORT _REGEX = /^@import \"(.*?)\";$/
var bootstrapLessLines = _ _less [ 'bootstrap.less' ] . split ( '\n' )
for ( var i = 0 , imports = [ ] ; i < bootstrapLessLines . length ; i ++ ) {
var match = IMPORT _REGEX . exec ( bootstrapLessLines [ i ] )
if ( match ) imports . push ( match [ 1 ] )
}
return imports
}
2013-08-12 03:42:13 +02:00
function generateCSS ( ) {
2013-09-24 08:29:45 +02:00
var oneChecked = false
var lessFileIncludes = { }
$ ( '#less-section input' ) . each ( function ( ) {
var $this = $ ( this )
var checked = $this . is ( ':checked' )
lessFileIncludes [ $this . val ( ) ] = checked
oneChecked = oneChecked || checked
} )
2013-08-12 03:42:13 +02:00
2013-09-24 08:29:45 +02:00
if ( ! oneChecked ) return false
2013-08-12 03:42:13 +02:00
var result = { }
var vars = { }
var css = ''
$ ( '#less-variables-section input' )
. each ( function ( ) {
2013-09-18 20:37:55 +02:00
$ ( this ) . val ( ) && ( vars [ $ ( this ) . prev ( ) . text ( ) ] = $ ( this ) . val ( ) )
2013-08-12 03:42:13 +02:00
} )
2013-09-24 08:29:45 +02:00
$ . each ( bootstrapLessFilenames ( ) , function ( index , filename ) {
var fileInclude = lessFileIncludes [ filename ]
// Files not explicitly unchecked are compiled into the final stylesheet.
// Core stylesheets like 'normalize.less' are not included in the form
// since disabling them would wreck everything, and so their 'fileInclude'
// will be 'undefined'.
if ( fileInclude || ( fileInclude == null ) ) css += _ _less [ filename ]
// Custom variables are added after Bootstrap variables so the custom
// ones take precedence.
if ( ( 'variables.less' === filename ) && vars ) css += generateCustomCSS ( vars )
} )
2013-08-12 03:42:13 +02:00
css = css . replace ( /@import[^\n]*/gi , '' ) //strip any imports
try {
var parser = new less . Parser ( {
paths : [ 'variables.less' , 'mixins.less' ]
, optimization : 0
, filename : 'bootstrap.css'
} ) . parse ( css , function ( err , tree ) {
2013-08-18 04:04:41 +02:00
if ( err ) {
2013-08-19 08:36:36 +02:00
return showError ( '<strong>Ruh roh!</strong> Could not parse less files.' , err )
2013-08-18 04:04:41 +02:00
}
2013-08-12 03:42:13 +02:00
result = {
'bootstrap.css' : cw + tree . toCSS ( ) ,
2013-12-03 22:31:47 +01:00
'bootstrap.min.css' : cw + tree . toCSS ( { compress : true } ) . replace ( /\n/g , '' ) // FIXME: remove newline hack once less.js upgraded to v1.4
2013-08-12 03:42:13 +02:00
}
} )
} catch ( err ) {
2013-08-19 08:36:36 +02:00
return showError ( '<strong>Ruh roh!</strong> Could not parse less files.' , err )
2013-08-12 03:42:13 +02:00
}
return result
}
function generateJavascript ( ) {
var $checked = $ ( '#plugin-section input:checked' )
if ( ! $checked . length ) return false
var js = $checked
. map ( function ( ) { return _ _js [ this . value ] } )
. toArray ( )
. join ( '\n' )
return {
'bootstrap.js' : js ,
'bootstrap.min.js' : cw + uglify ( js )
}
}
var inputsComponent = $ ( '#less-section input' )
var inputsPlugin = $ ( '#plugin-section input' )
var inputsVariables = $ ( '#less-variables-section input' )
$ ( '#less-section .toggle' ) . on ( 'click' , function ( e ) {
e . preventDefault ( )
inputsComponent . prop ( 'checked' , ! inputsComponent . is ( ':checked' ) )
} )
$ ( '#plugin-section .toggle' ) . on ( 'click' , function ( e ) {
e . preventDefault ( )
inputsPlugin . prop ( 'checked' , ! inputsPlugin . is ( ':checked' ) )
} )
$ ( '#less-variables-section .toggle' ) . on ( 'click' , function ( e ) {
e . preventDefault ( )
inputsVariables . val ( '' )
} )
2013-08-18 06:50:01 +02:00
$ ( '[data-dependencies]' ) . on ( 'click' , function ( ) {
if ( ! $ ( this ) . is ( ':checked' ) ) return
var dependencies = this . getAttribute ( 'data-dependencies' )
if ( ! dependencies ) return
dependencies = dependencies . split ( ',' )
for ( var i = 0 ; i < dependencies . length ; i ++ ) {
var dependency = $ ( '[value="' + dependencies [ i ] + '"]' )
dependency && dependency . prop ( 'checked' , true )
}
} )
$ ( '[data-dependents]' ) . on ( 'click' , function ( ) {
if ( $ ( this ) . is ( ':checked' ) ) return
var dependents = this . getAttribute ( 'data-dependents' )
if ( ! dependents ) return
dependents = dependents . split ( ',' )
for ( var i = 0 ; i < dependents . length ; i ++ ) {
var dependent = $ ( '[value="' + dependents [ i ] + '"]' )
dependent && dependent . prop ( 'checked' , false )
}
} )
2013-08-18 09:28:52 +02:00
var $compileBtn = $ ( '#btn-compile' )
var $downloadBtn = $ ( '#btn-download' )
$compileBtn . on ( 'click' , function ( e ) {
2013-09-14 10:26:42 +02:00
var configData = getCustomizerData ( )
var configJson = JSON . stringify ( configData , null , 2 )
2013-08-18 09:28:52 +02:00
e . preventDefault ( )
$compileBtn . attr ( 'disabled' , 'disabled' )
2013-09-14 10:26:42 +02:00
generateZip ( generateCSS ( ) , generateJavascript ( ) , generateFonts ( ) , configJson , function ( blob ) {
2013-08-18 09:28:52 +02:00
$compileBtn . removeAttr ( 'disabled' )
2013-09-18 18:50:02 +02:00
saveAs ( blob , 'bootstrap.zip' )
2013-09-14 10:26:42 +02:00
createGist ( configJson )
2013-08-18 09:28:52 +02:00
} )
} )
// browser support alerts
if ( ! window . URL && navigator . userAgent . toLowerCase ( ) . indexOf ( 'safari' ) != - 1 ) {
2013-09-18 18:50:02 +02:00
showCallout ( 'Looks like you\'re using safari, which sadly doesn\' t have the best support \
for HTML5 blobs . Because of this your file will be downloaded with the name < code > "untitled" < / c o d e > . \
However , if you check your downloads folder , just rename this < code > "untitled" < / c o d e > f i l e \
to < code > "bootstrap.zip" < / c o d e > a n d y o u s h o u l d b e g o o d t o g o ! ' )
2013-08-18 09:28:52 +02:00
} else if ( ! window . URL && ! window . webkitURL ) {
$ ( '.bs-docs-section, .bs-sidebar' ) . css ( 'display' , 'none' )
2013-09-18 18:50:02 +02:00
showCallout ( 'Looks like your current browser doesn\' t support the Bootstrap Customizer . Please take a second \
to < a href = "https://www.google.com/intl/en/chrome/browser/" > upgrade to a more modern browser < / a > . ' , t r u e )
2013-08-18 09:28:52 +02:00
}
2013-08-18 03:41:36 +02:00
parseUrl ( )
2013-08-19 08:27:46 +02:00
}