diff --git a/.csscomb.json b/.csscomb.json new file mode 100644 index 0000000000..1baf6bedd6 --- /dev/null +++ b/.csscomb.json @@ -0,0 +1,290 @@ +{ + "always-semicolon": true, + "block-indent": 2, + "colon-space": true, + "color-case": "lower", + "color-shorthand": true, + "combinator-space": true, + "element-case": "lower", + "eof-newline": true, + "leading-zero": false, + "remove-empty-rulesets": true, + "rule-indent": 2, + "stick-brace": true, + "strip-spaces": true, + "unitless-zero": true, + "vendor-prefix-align": true, + "sort-order": [ + [ + "position", + "top", + "right", + "bottom", + "left", + "z-index", + "display", + "float", + "width", + "min-width", + "max-width", + "height", + "min-height", + "max-height", + "-webkit-box-sizing", + "-moz-box-sizing", + "box-sizing", + "padding", + "padding-top", + "padding-right", + "padding-bottom", + "padding-left", + "margin", + "margin-top", + "margin-right", + "margin-bottom", + "margin-left", + "overflow", + "overflow-x", + "overflow-y", + "-ms-overflow-x", + "-ms-overflow-y", + "clip", + "clear", + "font", + "font-family", + "font-size", + "font-style", + "font-weight", + "font-variant", + "font-size-adjust", + "font-stretch", + "font-effect", + "font-emphasize", + "font-emphasize-position", + "font-emphasize-style", + "font-smooth", + "-webkit-hyphens", + "-moz-hyphens", + "hyphens", + "line-height", + "color", + "text-align", + "-webkit-text-align-last", + "-moz-text-align-last", + "-ms-text-align-last", + "text-align-last", + "text-emphasis", + "text-emphasis-color", + "text-emphasis-style", + "text-emphasis-position", + "text-decoration", + "text-indent", + "text-justify", + "text-outline", + "-ms-text-overflow", + "text-overflow", + "text-overflow-ellipsis", + "text-overflow-mode", + "text-shadow", + "text-transform", + "text-wrap", + "letter-spacing", + "-ms-word-break", + "word-break", + "word-spacing", + "-ms-word-wrap", + "word-wrap", + "-moz-tab-size", + "-o-tab-size", + "tab-size", + "white-space", + "vertical-align", + "list-style", + "list-style-position", + "list-style-type", + "list-style-image", + "pointer-events", + "cursor", + "visibility", + "zoom", + "flex-direction", + "flex-order", + "flex-pack", + "flex-align", + "table-layout", + "empty-cells", + "caption-side", + "border-spacing", + "border-collapse", + "content", + "quotes", + "counter-reset", + "counter-increment", + "resize", + "-webkit-user-select", + "-moz-user-select", + "-ms-user-select", + "user-select", + "nav-index", + "nav-up", + "nav-right", + "nav-down", + "nav-left", + "filter:progid:DXImageTransform.Microsoft.AlphaImageLoader", + "background", + "background-color", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.gradient", + "filter:progid:DXImageTransform.Microsoft.gradient", + "background-image", + "background-repeat", + "background-attachment", + "background-position", + "background-position-x", + "background-position-y", + "-webkit-background-clip", + "-moz-background-clip", + "background-clip", + "background-origin", + "-webkit-background-size", + "-moz-background-size", + "-o-background-size", + "background-size", + "border", + "border-color", + "border-style", + "border-width", + "border-top", + "border-top-color", + "border-top-style", + "border-top-width", + "border-right", + "border-right-color", + "border-right-style", + "border-right-width", + "border-bottom", + "border-bottom-color", + "border-bottom-style", + "border-bottom-width", + "border-left", + "border-left-color", + "border-left-style", + "border-left-width", + "border-radius", + "border-top-left-radius", + "border-top-right-radius", + "border-bottom-right-radius", + "border-bottom-left-radius", + "-webkit-border-image", + "-moz-border-image", + "-o-border-image", + "border-image", + "-webkit-border-image-source", + "-moz-border-image-source", + "-o-border-image-source", + "border-image-source", + "-webkit-border-image-slice", + "-moz-border-image-slice", + "-o-border-image-slice", + "border-image-slice", + "-webkit-border-image-width", + "-moz-border-image-width", + "-o-border-image-width", + "border-image-width", + "-webkit-border-image-outset", + "-moz-border-image-outset", + "-o-border-image-outset", + "border-image-outset", + "-webkit-border-image-repeat", + "-moz-border-image-repeat", + "-o-border-image-repeat", + "border-image-repeat", + "outline", + "outline-width", + "outline-style", + "outline-color", + "outline-offset", + "-webkit-box-shadow", + "-moz-box-shadow", + "box-shadow", + "filter:progid:DXImageTransform.Microsoft.Alpha(Opacity", + "-ms-filter:\\'progid:DXImageTransform.Microsoft.Alpha", + "opacity", + "-ms-interpolation-mode", + "-webkit-transition", + "-moz-transition", + "-ms-transition", + "-o-transition", + "transition", + "-webkit-transition-delay", + "-moz-transition-delay", + "-ms-transition-delay", + "-o-transition-delay", + "transition-delay", + "-webkit-transition-timing-function", + "-moz-transition-timing-function", + "-ms-transition-timing-function", + "-o-transition-timing-function", + "transition-timing-function", + "-webkit-transition-duration", + "-moz-transition-duration", + "-ms-transition-duration", + "-o-transition-duration", + "transition-duration", + "-webkit-transition-property", + "-moz-transition-property", + "-ms-transition-property", + "-o-transition-property", + "transition-property", + "-webkit-transform", + "-moz-transform", + "-ms-transform", + "-o-transform", + "transform", + "-webkit-transform-origin", + "-moz-transform-origin", + "-ms-transform-origin", + "-o-transform-origin", + "transform-origin", + "-webkit-animation", + "-moz-animation", + "-ms-animation", + "-o-animation", + "animation", + "-webkit-animation-name", + "-moz-animation-name", + "-ms-animation-name", + "-o-animation-name", + "animation-name", + "-webkit-animation-duration", + "-moz-animation-duration", + "-ms-animation-duration", + "-o-animation-duration", + "animation-duration", + "-webkit-animation-play-state", + "-moz-animation-play-state", + "-ms-animation-play-state", + "-o-animation-play-state", + "animation-play-state", + "-webkit-animation-timing-function", + "-moz-animation-timing-function", + "-ms-animation-timing-function", + "-o-animation-timing-function", + "animation-timing-function", + "-webkit-animation-delay", + "-moz-animation-delay", + "-ms-animation-delay", + "-o-animation-delay", + "animation-delay", + "-webkit-animation-iteration-count", + "-moz-animation-iteration-count", + "-ms-animation-iteration-count", + "-o-animation-iteration-count", + "animation-iteration-count", + "-webkit-animation-direction", + "-moz-animation-direction", + "-ms-animation-direction", + "-o-animation-direction", + "animation-direction" + ] + ] +} diff --git a/.csslintrc b/.csslintrc new file mode 100644 index 0000000000..8cf2f846df --- /dev/null +++ b/.csslintrc @@ -0,0 +1,18 @@ +{ + "adjoining-classes": false, + "box-sizing": false, + "box-model": false, + "compatible-vendor-prefixes": false, + "floats": false, + "font-sizes": false, + "gradients": false, + "important": false, + "known-properties": false, + "outline-none": false, + "qualified-headings": false, + "regex-selectors": false, + "text-indent": false, + "unique-headings": false, + "universal-selector": false, + "unqualified-attributes": false +} diff --git a/.gitattributes b/.gitattributes new file mode 100644 index 0000000000..0c6b2fea5f --- /dev/null +++ b/.gitattributes @@ -0,0 +1,8 @@ +# Enforce Unix newlines +*.css text eol=lf +*.html text eol=lf +*.js text eol=lf +*.json text eol=lf +*.less text eol=lf +*.md text eol=lf +*.yml text eol=lf diff --git a/.gitignore b/.gitignore index 10c58cf685..465cdb4633 100644 --- a/.gitignore +++ b/.gitignore @@ -39,3 +39,4 @@ validation-report.json # Folders to ignore node_modules +bower_components diff --git a/.travis.yml b/.travis.yml index 775a7f15aa..78a89aabf6 100644 --- a/.travis.yml +++ b/.travis.yml @@ -1,11 +1,18 @@ language: node_js node_js: - 0.10 -before_script: - - gem install jekyll - - npm install -g grunt-cli +before_install: + - time sudo pip install --use-mirrors -r ./test-infra/requirements.txt +install: + - time gem install jekyll + - time npm install -g grunt-cli + - time ./test-infra/node_modules_cache.py download || time npm install +after_script: + - time ./test-infra/node_modules_cache.py upload env: global: - - secure: Besg41eyU+2mfxrywQ4ydOShMdc34ImaO0S0ENP+aCOBuyNBIgP59wy5tBMmyai2/8eInYeVps4Td96mWInMMxzTe3Bar7eTLG5tWVKRSr/wc4NBPZ/ppoPAmCEsz9Y+VptRH9/FO8n7hsL9EFZ+xBKbG+C0SccGoyBDpA5j7/w= - - secure: Ptiv7phCImFP3ALIz+sMQzrZg8k7C1gLZbFBhWxjnQr3g06wIfX3Ls5y9OHvxid+lOZZjISui3wzBVgpVHqwHUYf96+r0mo6/mJ+F4ffUmShZANVaIMD/JRTnXhUQJbvntGLvxn1EYWPdNM+2IHJrMipnjHxU9tkgAnlel4Zdew= - - TWBS_HAVE_OWN_BROWSERSTACK_KEY: "" + - SAUCE_USERNAME: bootstrap + - secure: "pJkBwnuae9dKU5tEcCqccfS1QQw7/meEcfz63fM7ba7QJNjoA6BaXj08L5Z3Vb5vBmVPwBawxo5Hp0jC0r/Z/O0hGnAmz/Cz09L+cy7dSAZ9x4hvZePSja/UAusaB5ogMoO8l2b773MzgQeSmrLbExr9BWLeqEfjC2hFgdgHLaQ=" + - secure: "gqjqISbxBJK6byFbsmr1AyP1qoWH+rap06A2gI7v72+Tn2PU2nYkIMUkCvhZw6K889jv+LhQ/ybcBxDOXHpNCExCnSgB4dcnmYp+9oeNZb37jSP0rQ+Ib4OTLjzc3/FawE/fUq5kukZTC7porzc/k0qJNLAZRx3YLALmK1GIdUY=" + - secure: "Gghh/e3Gsbj1+4RR9Lh2aR/xJl35HWiHqlPIeSUqE9D7uDCVTAwNce/dGL3Ew7uJPfJ6Pgr70wD3zgu3stw0Zmzayax0hiDtGwcQCxVIER08wqGANK9C2Q7PYJkNTNtiTo6ehKWbdV4Z+/U+TEYyQfpQTDbAFYk/vVpsdjp0Lmc=" + - secure: "RTbRdx4G/2OTLfrZtP1VbRljxEmd6A1F3GqXboeQTldsnAlwpsES65es5CE3ub/rmixLApOY9ot7OPmNixFgC2Y8xOsV7lNCC62QVpmqQEDyGFFQKb3yO6/dmwQxdsCqGfzf9Np6Wh5V22QFvr50ZLKLd7Uhd9oXMDIk/z1MJ3o=" diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 96b7732b0d..708ace7ec9 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -17,7 +17,7 @@ We only accept issues that are bug reports or feature requests. Bugs must be iso ## Pull requests -- CSS changes must be done in `.less` files first, never just the compiled `.css` files +- CSS changes must be done in `.less` files first, never just in the compiled `.css` files - If modifying the `.less` files, always recompile and commit the compiled files `bootstrap.css` and `bootstrap.min.css` - Try not to pollute your pull request with unintended changes--keep them simple and small - Try to share which browsers your code has been tested in before submitting a pull request @@ -58,4 +58,22 @@ We only accept issues that are bug reports or feature requests. Bugs must be iso With v3.1, we're moving from the Apache 2 to the MIT license for the Bootstrap code (not the docs). We're in the process of collecting permissions from all Bootstrap contributors with code still part of the project to make this happen. For details, please see [#2054](https://github.com/twbs/bootstrap/issues/2054). -By contributing your code, you agree to dual-license your contribution under the [Apache 2](https://github.com/twbs/bootstrap/blob/master/LICENSE) and [MIT](https://github.com/twbs/bootstrap/blob/master/MIT) licenses. +By contributing your code, you agree to dual-license your contribution under the [Apache 2](https://github.com/twbs/bootstrap/blob/master/LICENSE) and [MIT](https://github.com/twbs/bootstrap/blob/master/LICENSE-MIT) licenses. + + + +## Release checklist + +1. Close ship list issue for the release. +2. Close the milestone for the release. +3. Open new release issue that includes this checklist. +4. Ping folks to coordinate release (mainly @jdorfman for BootstrapCDN). +5. Update version numbers using `grunt change-version-number --oldver=A.B.C --newver=X.Y.Z`. Review the changes and stage them manually. +6. Run `grunt` one last time. +7. Push to `master` branch. +8. Merge `master` into `gh-pages`. +9. Generate `bootstrap-X.Y.Z-dist.zip` file for release. +10. Create release on GitHub with `/dist/` folder and release notes. +11. Push `gh-pages`. +12. Publish blog post. +13. Tweet tweet. diff --git a/Gruntfile.js b/Gruntfile.js index c3016a2b00..aae80dc3df 100644 --- a/Gruntfile.js +++ b/Gruntfile.js @@ -1,7 +1,10 @@ /* jshint node: true */ -module.exports = function(grunt) { - "use strict"; +module.exports = function (grunt) { + 'use strict'; + + // Force use of Unix newlines + grunt.util.linefeed = '\n'; RegExp.quote = require('regexp-quote') var btoa = require('btoa') @@ -11,11 +14,9 @@ module.exports = function(grunt) { // Metadata. pkg: grunt.file.readJSON('package.json'), banner: '/*!\n' + - ' * Bootstrap v<%= pkg.version %> by @fat and @mdo\n' + + ' * Bootstrap v<%= pkg.version %> (<%= pkg.homepage %>)\n' + ' * Copyright <%= grunt.template.today("yyyy") %> <%= pkg.author %>\n' + ' * Licensed under <%= _.pluck(pkg.licenses, "url").join(", ") %>\n' + - ' *\n' + - ' * Designed and built with all the love in the world by @mdo and @fat.\n' + ' */\n\n', jqueryCheck: 'if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery") }\n\n', @@ -36,9 +37,34 @@ module.exports = function(grunt) { }, test: { src: ['js/tests/unit/*.js'] + }, + assets: { + src: ['docs-assets/js/application.js', 'docs-assets/js/customizer.js'] } }, + jscs: { + options: { + config: 'js/.jscs.json', + }, + gruntfile: { + src: ['Gruntfile.js'] + }, + src: { + src: ['js/*.js'] + }, + test: { + src: ['js/tests/unit/*.js'] + } + }, + + csslint: { + options: { + csslintrc: '.csslintrc' + }, + src: ['dist/css/bootstrap.css', 'dist/css/bootstrap-theme.css'] + }, + concat: { options: { banner: '<%= banner %><%= jqueryCheck %>', @@ -71,42 +97,74 @@ module.exports = function(grunt) { bootstrap: { src: ['<%= concat.bootstrap.dest %>'], dest: 'dist/js/<%= pkg.name %>.min.js' + }, + customize: { + src: [ + 'docs-assets/js/less.js', + 'docs-assets/js/jszip.js', + 'docs-assets/js/uglify.js', + 'docs-assets/js/filesaver.js', + 'docs-assets/js/customizer.js' + ], + dest: 'docs-assets/js/customize.js' } }, - recess: { - options: { - compile: true, - banner: '<%= banner %>' - }, - bootstrap: { - src: ['less/bootstrap.less'], - dest: 'dist/css/<%= pkg.name %>.css' - }, - min: { + less: { + compile: { options: { - compress: true + strictMath: true }, - src: ['less/bootstrap.less'], - dest: 'dist/css/<%= pkg.name %>.min.css' + files: { + 'dist/css/<%= pkg.name %>.css': 'less/bootstrap.less', + 'dist/css/<%= pkg.name %>-theme.css': 'less/theme.less' + } }, - theme: { - src: ['less/theme.less'], - dest: 'dist/css/<%= pkg.name %>-theme.css' - }, - theme_min: { + minify: { options: { - compress: true + cleancss: true, + report: 'min' }, - src: ['less/theme.less'], - dest: 'dist/css/<%= pkg.name %>-theme.min.css' + files: { + 'dist/css/<%= pkg.name %>.min.css': 'dist/css/<%= pkg.name %>.css', + 'dist/css/<%= pkg.name %>-theme.min.css': 'dist/css/<%= pkg.name %>-theme.css' + } + } + }, + + usebanner: { + dist: { + options: { + position: 'top', + banner: '<%= banner %>' + }, + files: { + src: [ + 'dist/css/<%= pkg.name %>.css', + 'dist/css/<%= pkg.name %>.min.css', + 'dist/css/<%= pkg.name %>-theme.css', + 'dist/css/<%= pkg.name %>-theme.min.css', + ] + } + } + }, + + csscomb: { + sort: { + options: { + sortOrder: '.csscomb.json' + }, + files: { + 'dist/css/<%= pkg.name %>.css': ['dist/css/<%= pkg.name %>.css'], + 'dist/css/<%= pkg.name %>-theme.css': ['dist/css/<%= pkg.name %>-theme.css'], + } } }, copy: { fonts: { expand: true, - src: ["fonts/*"], + src: ['fonts/*'], dest: 'dist/' } }, @@ -135,12 +193,12 @@ module.exports = function(grunt) { options: { reset: true, relaxerror: [ - "Bad value X-UA-Compatible for attribute http-equiv on element meta.", - "Element img is missing required attribute src." + 'Bad value X-UA-Compatible for attribute http-equiv on element meta.', + 'Element img is missing required attribute src.' ] }, files: { - src: ["_gh_pages/**/*.html"] + src: ['_gh_pages/**/*.html'] } }, @@ -153,9 +211,9 @@ module.exports = function(grunt) { files: '<%= jshint.test.src %>', tasks: ['jshint:test', 'qunit'] }, - recess: { + less: { files: 'less/*.less', - tasks: ['recess'] + tasks: ['less'] } }, @@ -168,36 +226,116 @@ module.exports = function(grunt) { replacement: grunt.option('newver'), recursive: true } + }, + + 'saucelabs-qunit': { + all: { + options: { + build: process.env.TRAVIS_JOB_ID, + concurrency: 3, + urls: ['http://127.0.0.1:3000/js/tests/index.html'], + browsers: [ + // See https://saucelabs.com/docs/platforms/webdriver + { + browserName: 'safari', + version: '7', + platform: 'OS X 10.9' + }, + { + browserName: 'chrome', + version: '31', + platform: 'OS X 10.9' + }, + /* FIXME: currently fails 1 tooltip test + { + browserName: 'firefox', + version: '25', + platform: 'OS X 10.6' + },*/ + // Mac Opera not currently supported by Sauce Labs + /* FIXME: currently fails 1 tooltip test + { + browserName: 'internet explorer', + version: '11', + platform: 'Windows 8.1' + },*/ + /* + { + browserName: 'internet explorer', + version: '10', + platform: 'Windows 8' + }, + { + browserName: 'internet explorer', + version: '9', + platform: 'Windows 7' + }, + { + browserName: 'internet explorer', + version: '8', + platform: 'Windows 7' + }, + {// unofficial + browserName: 'internet explorer', + version: '7', + platform: 'Windows XP' + }, + */ + { + browserName: 'chrome', + version: '31', + platform: 'Windows 8.1' + }, + { + browserName: 'firefox', + version: '25', + platform: 'Windows 8.1' + }, + // Win Opera 15+ not currently supported by Sauce Labs + { + browserName: 'iphone', + version: '6.1', + platform: 'OS X 10.8' + }, + // iOS Chrome not currently supported by Sauce Labs + // Linux (unofficial) + { + browserName: 'chrome', + version: '30', + platform: 'Linux' + }, + { + browserName: 'firefox', + version: '25', + platform: 'Linux' + } + // Android Chrome not currently supported by Sauce Labs + /* Android Browser (super-unofficial) + { + browserName: 'android', + version: '4.0', + platform: 'Linux' + } + */ + ], + } + } } }); // These plugins provide necessary tasks. - grunt.loadNpmTasks('browserstack-runner'); - grunt.loadNpmTasks('grunt-contrib-clean'); - grunt.loadNpmTasks('grunt-contrib-concat'); - grunt.loadNpmTasks('grunt-contrib-connect'); - grunt.loadNpmTasks('grunt-contrib-copy'); - grunt.loadNpmTasks('grunt-contrib-jshint'); - grunt.loadNpmTasks('grunt-contrib-qunit'); - grunt.loadNpmTasks('grunt-contrib-uglify'); - grunt.loadNpmTasks('grunt-contrib-watch'); - grunt.loadNpmTasks('grunt-html-validation'); - grunt.loadNpmTasks('grunt-jekyll'); - grunt.loadNpmTasks('grunt-recess'); - grunt.loadNpmTasks('grunt-sed'); + require('load-grunt-tasks')(grunt, {scope: 'devDependencies'}); // Docs HTML validation task grunt.registerTask('validate-html', ['jekyll', 'validation']); // Test task. - var testSubtasks = ['dist-css', 'jshint', 'qunit', 'validate-html']; - // Only run BrowserStack tests under Travis - if (process.env.TRAVIS) { - // Only run BrowserStack tests if this is a mainline commit in twbs/bootstrap, or you have your own BrowserStack key - if ((process.env.TRAVIS_REPO_SLUG === 'twbs/bootstrap' && process.env.TRAVIS_PULL_REQUEST === 'false') || process.env.TWBS_HAVE_OWN_BROWSERSTACK_KEY) { - testSubtasks.push('browserstack_runner'); - } + var testSubtasks = ['dist-css', 'jshint', 'jscs', 'qunit', 'validate-html']; + // Only run Sauce Labs tests if there's a Sauce access key + if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') { + testSubtasks.push('connect'); + testSubtasks.push('saucelabs-qunit'); } grunt.registerTask('test', testSubtasks); @@ -205,7 +343,7 @@ module.exports = function(grunt) { grunt.registerTask('dist-js', ['concat', 'uglify']); // CSS distribution task. - grunt.registerTask('dist-css', ['recess']); + grunt.registerTask('dist-css', ['less', 'csscomb', 'usebanner']); // Fonts distribution task. grunt.registerTask('dist-fonts', ['copy']); diff --git a/README.md b/README.md index 72a3935d93..7d342d7a70 100644 --- a/README.md +++ b/README.md @@ -1,4 +1,5 @@ # [Bootstrap](http://getbootstrap.com) [![Build Status](https://secure.travis-ci.org/twbs/bootstrap.png)](http://travis-ci.org/twbs/bootstrap) [![devDependency Status](https://david-dm.org/twbs/bootstrap/dev-status.png)](https://david-dm.org/twbs/bootstrap#info=devDependencies) +[![Selenium Test Status](https://saucelabs.com/browser-matrix/bootstrap.svg)](https://saucelabs.com/u/bootstrap) Bootstrap is a sleek, intuitive, and powerful front-end framework for faster and easier web development, created and maintained by [Mark Otto](http://twitter.com/mdo) and [Jacob Thornton](http://twitter.com/fat). @@ -10,7 +11,7 @@ To get started, check out ! Three quick start options are available: -* [Download the latest release](https://github.com/twbs/bootstrap/archive/v3.0.2.zip). +* [Download the latest release](https://github.com/twbs/bootstrap/archive/v3.0.3.zip). * Clone the repo: `git clone https://github.com/twbs/bootstrap.git`. * Install with [Bower](http://bower.io): `bower install bootstrap`. @@ -56,8 +57,9 @@ Bootstrap's documentation, included in this repo in the root directory, is built ### Running documentation locally 1. If necessary, [install Jekyll](http://jekyllrb.com/docs/installation) (requires v1.x). + - **Windows users:** read [this unofficial guide](https://github.com/juthilo/run-jekyll-on-windows/) to get Jekyll up and running without problems. 2. From the root `/bootstrap` directory, run `jekyll serve` in the command line. - - **Windows users:** run `chcp 65001` first to change the command prompt's character encoding ([code page](http://en.wikipedia.org/wiki/Windows_code_page)) to UTF-8 so Jekyll runs without errors. + - **Windows users:** For Ruby 2.0.0 run `chcp 65001` first to change the command prompt's character encoding ([code page](http://en.wikipedia.org/wiki/Windows_code_page)) to UTF-8 so Jekyll runs without errors. For Ruby 1.9.3 you can alternatively do `SET LANG=en_EN.UTF-8`. In addition, ensure you have Python installed and added in your `PATH` or the build will fail due to our Pygments dependency. 3. Open in your browser, and voilà. Learn more about using Jekyll by reading its [documentation](http://jekyllrb.com/docs/home/). @@ -122,7 +124,6 @@ Keep track of development and community news. * Follow [@twbootstrap on Twitter](http://twitter.com/twbootstrap). * Read and subscribe to [The Official Bootstrap Blog](http://blog.getbootstrap.com). -* Have a question that's not a feature request or bug report? [Ask on the mailing list.](http://groups.google.com/group/twitter-bootstrap) * Chat with fellow Bootstrappers in IRC. On the `irc.freenode.net` server, in the `##twitter-bootstrap` channel. diff --git a/_config.yml b/_config.yml index 0433cf0d99..02f2f4989b 100644 --- a/_config.yml +++ b/_config.yml @@ -11,15 +11,15 @@ exclude: [".editorconfig", ".gitignore", "bower.json", "composer.json", port: 9001 # Custom vars -current_version: 3.0.2 +current_version: 3.0.3 repo: https://github.com/twbs/bootstrap -download_source: https://github.com/twbs/bootstrap/archive/v3.0.2.zip -download_dist: https://github.com/twbs/bootstrap/releases/download/v3.0.2/bootstrap-3.0.2-dist.zip +download_source: https://github.com/twbs/bootstrap/archive/v3.0.3.zip +download_dist: https://github.com/twbs/bootstrap/releases/download/v3.0.3/bootstrap-3.0.3-dist.zip blog: http://blog.getbootstrap.com expo: http://expo.getbootstrap.com -cdn_css: //netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap.min.css -cdn_theme_css: //netdna.bootstrapcdn.com/bootstrap/3.0.2/css/bootstrap-theme.min.css -cdn_js: //netdna.bootstrapcdn.com/bootstrap/3.0.2/js/bootstrap.min.js +cdn_css: //netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap.min.css +cdn_theme_css: //netdna.bootstrapcdn.com/bootstrap/3.0.3/css/bootstrap-theme.min.css +cdn_js: //netdna.bootstrapcdn.com/bootstrap/3.0.3/js/bootstrap.min.js diff --git a/_includes/footer.html b/_includes/footer.html index 7f9f8f103b..095e96eb59 100644 --- a/_includes/footer.html +++ b/_includes/footer.html @@ -1,23 +1,33 @@ - - + + - {% if page.slug == "customize" %} - - - - - + {% endif %} +{% comment %} + Inject Twitter widgets asynchronously. Snippet snipped from Twitter's + JS interface site: https://dev.twitter.com/docs/tfw-javascript + + * "js.async=1;" added to add async attribute to the generated script tag. +{% endcomment %} + + + + + + diff --git a/examples/dashboard/dashboard.css b/examples/dashboard/dashboard.css new file mode 100644 index 0000000000..750124bf84 --- /dev/null +++ b/examples/dashboard/dashboard.css @@ -0,0 +1,93 @@ +/* + * Base structure + */ + +/* Move down content because we have a fixed navbar that is 50px tall */ +body { + padding-top: 50px; +} + + +/* + * Global add-ons + */ + +.sub-header { + padding-bottom: 10px; + border-bottom: 1px solid #eee; +} + + +/* + * Sidebar + */ + +/* Hide for mobile, show later */ +.sidebar { + display: none; +} +@media (min-width: 768px) { + .sidebar { + position: fixed; + top: 0; + left: 0; + bottom: 0; + z-index: 1000; + display: block; + padding: 70px 20px 20px; + background-color: #f5f5f5; + border-right: 1px solid #eee; + } +} + +/* Sidebar navigation */ +.nav-sidebar { + margin-left: -20px; + margin-right: -21px; /* 20px padding + 1px border */ + margin-bottom: 20px; +} +.nav-sidebar > li > a { + padding-left: 20px; + padding-right: 20px; +} +.nav-sidebar > .active > a { + color: #fff; + background-color: #428bca; +} + + +/* + * Main content + */ + +.main { + padding: 20px; +} +@media (min-width: 768px) { + .main { + padding-left: 40px; + pading-right: 40px; + } +} +.main .page-header { + margin-top: 0; +} + + +/* + * Placeholder dashboard ideas + */ + +.placeholders { + margin-bottom: 30px; + text-align: center; +} +.placeholders h4 { + margin-bottom: 0; +} +.placeholder { + margin-bottom: 20px; +} +.placeholder img { + border-radius: 50%; +} diff --git a/examples/dashboard/index.html b/examples/dashboard/index.html new file mode 100644 index 0000000000..e721aa16a3 --- /dev/null +++ b/examples/dashboard/index.html @@ -0,0 +1,243 @@ + + + + + + + + + + + Dashboard Template for Bootstrap + + + + + + + + + + + + + + + + + + +
+
+ +
+

Dashboard

+ +
+
+ Generic placeholder thumbnail +

Label

+ Something else +
+
+ Generic placeholder thumbnail +

Label

+ Something else +
+
+ Generic placeholder thumbnail +

Label

+ Something else +
+
+ Generic placeholder thumbnail +

Label

+ Something else +
+
+ +

Section title

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
#HeaderHeaderHeaderHeader
1,001Loremipsumdolorsit
1,002ametconsecteturadipiscingelit
1,003IntegernecodioPraesent
1,003liberoSedcursusante
1,004dapibusdiamSednisi
1,005Nullaquissemat
1,006nibhelementumimperdietDuis
1,007sagittisipsumPraesentmauris
1,008Fuscenectellussed
1,009auguesemperportaMauris
1,010massaVestibulumlaciniaarcu
1,011egetnullaClassaptent
1,012tacitisociosquadlitora
1,013torquentperconubianostra
1,014perinceptoshimenaeosCurabitur
1,015sodalesligulainlibero
+
+
+
+
+ + + + + + + + diff --git a/examples/jumbotron/index.html b/examples/jumbotron/index.html index 3e301a1040..b7ff7f76c2 100644 --- a/examples/jumbotron/index.html +++ b/examples/jumbotron/index.html @@ -40,7 +40,7 @@ Project name + + {% endhighlight %} @@ -235,6 +235,67 @@ $('#myModal').on('show.bs.modal', function (e) {

Additionally, you may give a description of your modal dialog with aria-describedby on .modal.

+

Optional sizes

+

Modals have two optional sizes, available via modifier classes to be placed on a .modal-dialog.

+
+ + +
+{% highlight html %} + + + + + + + + + +{% endhighlight %} + + + + + +

Usage

The modal plugin toggles your hidden content on demand, via data attributes or JavaScript. It also adds .model-open to the <body> to override default scrolling behavior and generates a .modal-backdrop to provide a click area for dismissing shown modals when clicking outside the modal.

@@ -304,15 +365,15 @@ $('#myModal').modal({ {% endhighlight %}

.modal('toggle')

-

Manually toggles a modal.

+

Manually toggles a modal. Returns to the caller before the modal has actually been shown or hidden (i.e. before the shown.bs.modal or hidden.bs.modal event occurs).

{% highlight js %}$('#myModal').modal('toggle'){% endhighlight %}

.modal('show')

-

Manually opens a modal.

+

Manually opens a modal. Returns to the caller before the modal has actually been shown (i.e. before the shown.bs.modal event occurs).

{% highlight js %}$('#myModal').modal('show'){% endhighlight %}

.modal('hide')

-

Manually hides a modal.

+

Manually hides a modal. Returns to the caller before the modal has actually been hidden (i.e. before the hidden.bs.modal event occurs).

{% highlight js %}$('#myModal').modal('hide'){% endhighlight %}

Events

@@ -328,11 +389,11 @@ $('#myModal').modal({ show.bs.modal - This event fires immediately when the show instance method is called. + This event fires immediately when the show instance method is called. If caused by a click, the clicked element is available as the relatedTarget property of the event. shown.bs.modal - This event is fired when the modal has been made visible to the user (will wait for CSS transitions to complete). + This event is fired when the modal has been made visible to the user (will wait for CSS transitions to complete). If caused by a click, the clicked element is available as the relatedTarget property of the event. hide.bs.modal @@ -346,8 +407,8 @@ $('#myModal').modal({ {% highlight js %} -$('#myModal').on('hidden.bs.modal', function () { - // do something… +$('#myModal').on('hidden.bs.modal', function (e) { + // do something... }) {% endhighlight %} @@ -415,7 +476,7 @@ $('#myModal').on('hidden.bs.modal', function () { -

Within tabs

+

Within pills

- +
@@ -529,6 +590,7 @@ $('#myDropdown').on('show.bs.dropdown', function () { {% endhighlight %} +
@@ -588,7 +650,13 @@ $('#myDropdown').on('show.bs.dropdown', function () {

Via data attributes

To easily add scrollspy behavior to your topbar navigation, add data-spy="scroll" to the element you want to spy on (most typically this would be the <body>). Then add the data-target attribute with the ID or class of the parent element of any Bootstrap .nav component.

{% highlight html %} - + + ... + ... {% endhighlight %} @@ -596,7 +664,7 @@ $('#myDropdown').on('show.bs.dropdown', function () {

Via JavaScript

Call the scrollspy via JavaScript:

{% highlight js %} -$('body').scrollspy({ target: '#navbar-example' }) +$('body').scrollspy({ target: '.navbar-example' }) {% endhighlight %}
@@ -1116,7 +1184,7 @@ $('#myTooltip').on('hidden.bs.tooltip', function () { animation boolean true - apply a CSS fade transition to the tooltip + apply a CSS fade transition to the popover html @@ -1134,7 +1202,7 @@ $('#myTooltip').on('hidden.bs.tooltip', function () { selector string false - if a selector is provided, tooltip objects will be delegated to the specified targets. in practice, this is used to enable dynamic HTML content to have popovers added. See this and an informative example. + if a selector is provided, tooltip objects will be delegated to the specified targets. In practice, this is used to enable dynamic HTML content to have popovers added. See this and an informative example. trigger @@ -1327,14 +1395,23 @@ $('#my-alert').bind('closed.bs.alert', function () {

Stateful

Add data-loading-text="Loading..." to use a loading state on a button.

-
{% highlight html %} - + {% endhighlight %}

Single toggle

@@ -1408,7 +1485,7 @@ $('#my-alert').bind('closed.bs.alert', function () {

Usage

Enable buttons via JavaScript:

{% highlight js %} -$('.btn-group').button() +$('.btn').button() {% endhighlight %}

Markup

@@ -1426,14 +1503,23 @@ $('.btn-group').button()

You can enable auto toggling of a button by using the data-toggle attribute.

{% highlight html %} - + {% endhighlight %}

$().button('loading')

Sets button state to loading - disables button and swaps text to loading text. Loading text should be defined on the button element using the data attribute data-loading-text.

{% highlight html %} - + + {% endhighlight %}
@@ -1447,7 +1533,7 @@ $('.btn-group').button()

$().button(string)

Resets button state - swaps text to any data defined text state.

{% highlight html %} - + @@ -1593,7 +1679,7 @@ $('.btn-group').button()

Via JavaScript

Enable manually with:

{% highlight js %} -$(".collapse").collapse() +$('.collapse').collapse() {% endhighlight %}

Options

@@ -1926,22 +2012,27 @@ $('#myCarousel').on('slide.bs.carousel', function () {

Usage

+

Use the affix plugin via data attributes or manually with your own JavaScript. In both situations, you must provide CSS for the positioning of your content.

+ +

Positioning via CSS

+

The affix plugin toggles between three classes, each representing a particular state: .affix, .affix-top, and .affix-bottom. You must provide the styles for these classes yourself (independent of this plugin) to handle the actual positions.

+

Here's how the affix plugin works:

+
    +
  1. To start, the plugin adds .affix-top to indicate the element is in it's top-most position. At this point no CSS positioning is required.
  2. +
  3. Scrolling past the element you want affixed should trigger the actual affixing. This is where .affix replaces .affix-top and sets position: fixed; (provided by Bootstrap's code CSS).
  4. +
  5. If a bottom offset is defined, scrolling past that should replace .affix with .affix-bottom. Since offsets are optional, setting one requires you to set the appropriate CSS. In this case, add position: absolute; when necessary. The plugin uses the data attribute or JavaScript option to determine where to position the element from there.
  6. +
+

Follow the above steps to set your CSS for either of the usage options below.

Via data attributes

-

To easily add affix behavior to any element, just add data-spy="affix" to the element you want to spy on. Then use offsets to define when to toggle the pinning of an element on and off.

+

To easily add affix behavior to any element, just add data-spy="affix" to the element you want to spy on. Use offsets to define when to toggle the pinning of an element.

{% highlight html %} -
...
+
+ ... +
{% endhighlight %} -
-

Requires independent styling ;)

-

- Affix toggles between three states/classes: .affix, .affix-top, and .affix-bottom. You must provide the styles for these classes yourself (independent of this plugin). - The .affix-top class should be in the regular flow of the document. The .affix class should be position: fixed. And .affix-bottom should be position: absolute. Note: .affix-bottom is special in that the plugin will place the element with JS relative to the offset: { bottom: number } option you've provided. -

-
-

Via JavaScript

Call the affix plugin via JavaScript:

{% highlight js %} diff --git a/js/.jscs.json b/js/.jscs.json new file mode 100644 index 0000000000..e02344fd5a --- /dev/null +++ b/js/.jscs.json @@ -0,0 +1,14 @@ +{ + "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"], + "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, + "disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], + "requireRightStickedOperators": ["!"], + "disallowRightStickedOperators": ["?", "/", "*", ":", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], + "disallowSpaceAfterPrefixUnaryOperators": ["++", "--", "+", "-", "~", "!"], + "disallowSpaceBeforePostfixUnaryOperators": ["++", "--"], + "requireSpaceBeforeBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], + "requireSpaceAfterBinaryOperators": ["+", "-", "/", "*", "=", "==", "===", "!=", "!=="], + "disallowKeywords": ["with"], + "validateLineBreaks": "LF", + "requireLineFeedAtFileEnd": true +} diff --git a/js/.jshintrc b/js/.jshintrc index fdfdfbbfb3..c8cccda377 100644 --- a/js/.jshintrc +++ b/js/.jshintrc @@ -10,5 +10,6 @@ "expr" : true, "laxbreak" : true, "laxcomma" : true, + "quotmark" : "single", "validthis": true } \ No newline at end of file diff --git a/js/affix.js b/js/affix.js index 7d111ecc4d..0650ce3ae5 100644 --- a/js/affix.js +++ b/js/affix.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: affix.js v3.0.2 + * Bootstrap: affix.js v3.0.3 * http://getbootstrap.com/javascript/#affix * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // AFFIX CLASS DEFINITION // ====================== diff --git a/js/alert.js b/js/alert.js index 031d72aa59..13a0cab915 100644 --- a/js/alert.js +++ b/js/alert.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: alert.js v3.0.2 + * Bootstrap: alert.js v3.0.3 * http://getbootstrap.com/javascript/#alerts * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // ALERT CLASS DEFINITION // ====================== diff --git a/js/button.js b/js/button.js index 0145689dde..6c66960d82 100644 --- a/js/button.js +++ b/js/button.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: button.js v3.0.2 + * Bootstrap: button.js v3.0.3 * http://getbootstrap.com/javascript/#buttons * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // BUTTON PUBLIC CLASS DEFINITION // ============================== @@ -54,15 +54,21 @@ Button.prototype.toggle = function () { var $parent = this.$element.closest('[data-toggle="buttons"]') + var changed = true if ($parent.length) { var $input = this.$element.find('input') - .prop('checked', !this.$element.hasClass('active')) - .trigger('change') - if ($input.prop('type') === 'radio') $parent.find('.active').removeClass('active') + if ($input.prop('type') === 'radio') { + // see if clicking on current one + if ($input.prop('checked') && this.$element.hasClass('active')) + changed = false + else + $parent.find('.active').removeClass('active') + } + if (changed) $input.prop('checked', !this.$element.hasClass('active')).trigger('change') } - this.$element.toggleClass('active') + if (changed) this.$element.toggleClass('active') } diff --git a/js/carousel.js b/js/carousel.js index 902d4d781e..26f3832ff4 100644 --- a/js/carousel.js +++ b/js/carousel.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: carousel.js v3.0.2 + * Bootstrap: carousel.js v3.0.3 * http://getbootstrap.com/javascript/#carousel * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // CAROUSEL CLASS DEFINITION // ========================= @@ -69,7 +69,7 @@ if (pos > (this.$items.length - 1) || pos < 0) return - if (this.sliding) return this.$element.one('slid', function () { that.to(pos) }) + if (this.sliding) return this.$element.one('slid.bs.carousel', function () { that.to(pos) }) if (activeIndex == pos) return this.pause().cycle() return this.slide(pos > activeIndex ? 'next' : 'prev', $(this.$items[pos])) @@ -121,7 +121,7 @@ if (this.$indicators.length) { this.$indicators.find('.active').removeClass('active') - this.$element.one('slid', function () { + this.$element.one('slid.bs.carousel', function () { var $nextIndicator = $(that.$indicators.children()[that.getActiveIndex()]) $nextIndicator && $nextIndicator.addClass('active') }) @@ -139,7 +139,7 @@ $next.removeClass([type, direction].join(' ')).addClass('active') $active.removeClass(['active', direction].join(' ')) that.sliding = false - setTimeout(function () { that.$element.trigger('slid') }, 0) + setTimeout(function () { that.$element.trigger('slid.bs.carousel') }, 0) }) .emulateTransitionEnd(600) } else { @@ -148,7 +148,7 @@ $active.removeClass('active') $next.addClass('active') this.sliding = false - this.$element.trigger('slid') + this.$element.trigger('slid.bs.carousel') } isCycling && this.cycle() diff --git a/js/collapse.js b/js/collapse.js index 9967b167f1..7bc9fcb425 100644 --- a/js/collapse.js +++ b/js/collapse.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: collapse.js v3.0.2 + * Bootstrap: collapse.js v3.0.3 * http://getbootstrap.com/javascript/#collapse * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // COLLAPSE PUBLIC CLASS DEFINITION // ================================ diff --git a/js/dropdown.js b/js/dropdown.js index d5da638def..3f71c7569b 100644 --- a/js/dropdown.js +++ b/js/dropdown.js @@ -1,5 +1,5 @@ /* ======================================================================== - * Bootstrap: dropdown.js v3.0.2 + * Bootstrap: dropdown.js v3.0.3 * http://getbootstrap.com/javascript/#dropdowns * ======================================================================== * Copyright 2013 Twitter, Inc. @@ -18,7 +18,7 @@ * ======================================================================== */ -+function ($) { "use strict"; ++function ($) { 'use strict'; // DROPDOWN CLASS DEFINITION // ========================= @@ -26,7 +26,7 @@ var backdrop = '.dropdown-backdrop' var toggle = '[data-toggle=dropdown]' var Dropdown = function (element) { - var $el = $(element).on('click.bs.dropdown', this.toggle) + $(element).on('click.bs.dropdown', this.toggle) } Dropdown.prototype.toggle = function (e) { @@ -41,7 +41,7 @@ if (!isActive) { if ('ontouchstart' in document.documentElement && !$parent.closest('.navbar-nav').length) { - // if mobile we we use a backdrop because click events don't delegate + // if mobile we use a backdrop because click events don't delegate $('