Commit fd62247b authored by Mark Otto's avatar Mark Otto

Merge branch 'master' into cover_template

parents fbf31f96 2b86e05a
language: node_js language: node_js
node_js: node_js:
- 0.10 - 0.10
before_script: before_install:
- gem install jekyll - time sudo pip install --use-mirrors -r ./test-infra/requirements.txt
- npm install -g grunt-cli install:
- if [ "$TWBS_TEST" = validate-html ]; then time gem install jekyll; fi
- time npm install -g grunt-cli
- time ./test-infra/node_modules_cache.py download || time npm install
after_script:
- if [ "$TWBS_TEST" = core ]; then time ./test-infra/node_modules_cache.py upload; fi
env: env:
global: global:
- SAUCE_USERNAME: bootstrap - SAUCE_USERNAME: bootstrap
- secure: "pJkBwnuae9dKU5tEcCqccfS1QQw7/meEcfz63fM7ba7QJNjoA6BaXj08L5Z3Vb5vBmVPwBawxo5Hp0jC0r/Z/O0hGnAmz/Cz09L+cy7dSAZ9x4hvZePSja/UAusaB5ogMoO8l2b773MzgQeSmrLbExr9BWLeqEfjC2hFgdgHLaQ=" - 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="
matrix:
- TWBS_TEST=core
- TWBS_TEST=validate-html
- TWBS_TEST=sauce-js-unit
matrix:
fast_finish: true
...@@ -238,13 +238,13 @@ module.exports = function (grunt) { ...@@ -238,13 +238,13 @@ module.exports = function (grunt) {
// See https://saucelabs.com/docs/platforms/webdriver // See https://saucelabs.com/docs/platforms/webdriver
{ {
browserName: 'safari', browserName: 'safari',
version: '6', version: '7',
platform: 'OS X 10.8' platform: 'OS X 10.9'
}, },
{ {
browserName: 'chrome', browserName: 'chrome',
version: '28', version: '31',
platform: 'OS X 10.6' platform: 'OS X 10.9'
}, },
/* FIXME: currently fails 1 tooltip test /* FIXME: currently fails 1 tooltip test
{ {
...@@ -331,9 +331,19 @@ module.exports = function (grunt) { ...@@ -331,9 +331,19 @@ module.exports = function (grunt) {
grunt.registerTask('validate-html', ['jekyll', 'validation']); grunt.registerTask('validate-html', ['jekyll', 'validation']);
// Test task. // Test task.
var testSubtasks = ['dist-css', 'jshint', 'jscs', 'qunit', 'validate-html']; var testSubtasks = [];
// Skip core tests if running a different subset of the test suite
if (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'core') {
testSubtasks = testSubtasks.concat(['dist-css', 'jshint', 'jscs', 'qunit']);
}
// Skip HTML validation if running a different subset of the test suite
if (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'validate-html') {
testSubtasks.push('validate-html');
}
// Only run Sauce Labs tests if there's a Sauce access key // Only run Sauce Labs tests if there's a Sauce access key
if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined') { if (typeof process.env.SAUCE_ACCESS_KEY !== 'undefined'
// Skip Sauce if running a different subset of the test suite
&& (!process.env.TWBS_TEST || process.env.TWBS_TEST === 'sauce-js-unit')) {
testSubtasks.push('connect'); testSubtasks.push('connect');
testSubtasks.push('saucelabs-qunit'); testSubtasks.push('saucelabs-qunit');
} }
......
...@@ -87,6 +87,8 @@ ...@@ -87,6 +87,8 @@
<li> <li>
<a href="#helper-classes">Helper classes</a> <a href="#helper-classes">Helper classes</a>
<ul class="nav"> <ul class="nav">
<li><a href="#helper-classes-colors">Contextual colors</a></li>
<li><a href="#helper-classes-backgrounds">Contextual backgrounds</a></li>
<li><a href="#helper-classes-close">Close icon</a></li> <li><a href="#helper-classes-close">Close icon</a></li>
<li><a href="#helper-classes-carets">Carets</a></li> <li><a href="#helper-classes-carets">Carets</a></li>
<li><a href="#helper-classes-floats">Quick floats</a></li> <li><a href="#helper-classes-floats">Quick floats</a></li>
...@@ -105,3 +107,11 @@ ...@@ -105,3 +107,11 @@
<li><a href="#responsive-utilities-tests">Test cases</a></li> <li><a href="#responsive-utilities-tests">Test cases</a></li>
</ul> </ul>
</li> </li>
<li>
<a href="#less">Using LESS</a>
<ul class="nav">
<li><a href="#less-variables">Variables</a></li>
<li><a href="#less-mixins-vendor">Vendor mixins</a></li>
<li><a href="#less-mixins-utility">Utility mixins</a></li>
</ul>
</li>
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
<a href="#modals">Modal</a> <a href="#modals">Modal</a>
<ul class="nav"> <ul class="nav">
<li><a href="#modals-examples">Examples</a></li> <li><a href="#modals-examples">Examples</a></li>
<li><a href="#modals-sizes">Sizes</a></li>
<li><a href="#modals-usage">Usage</a></li> <li><a href="#modals-usage">Usage</a></li>
</ul> </ul>
</li> </li>
......
...@@ -902,9 +902,13 @@ base_url: "../" ...@@ -902,9 +902,13 @@ base_url: "../"
{% endhighlight %} {% endhighlight %}
<h3 id="dropdowns-alignment">Alignment options</h3> <h3 id="dropdowns-alignment">Alignment options</h3>
<p>Add <code>.pull-right</code> to a <code>.dropdown-menu</code> to right align the dropdown menu.</p> <p>Add <code>.dropdown-menu-right</code> to a <code>.dropdown-menu</code> to right align the dropdown menu.</p>
<div class="bs-callout bs-callout-warning">
<h4>Deprecated <code>.pull-right</code> alignment</h4>
<p>As of v3.1, we've deprecated <code>.pull-right</code> on dropdown menus. To right-align a menu, use <code>.dropdown-menu-right</code>. Right-aligned nav components in the navbar use a mixin version of this class to automatically align the menu. To override it, use <code>.dropdown-menu-left</code>.</p>
</div>
{% highlight html %} {% highlight html %}
<ul class="dropdown-menu pull-right" role="menu" aria-labelledby="dLabel"> <ul class="dropdown-menu dropdown-menu-right" role="menu" aria-labelledby="dLabel">
... ...
</ul> </ul>
{% endhighlight %} {% endhighlight %}
...@@ -1099,7 +1103,7 @@ base_url: "../" ...@@ -1099,7 +1103,7 @@ base_url: "../"
{% endhighlight %} {% endhighlight %}
<h3 id="btn-groups-vertical">Vertical variation</h3> <h3 id="btn-groups-vertical">Vertical variation</h3>
<p>Make a set of buttons appear vertically stacked rather than horizontally.</p> <p>Make a set of buttons appear vertically stacked rather than horizontally. <strong class="text-danger">Split button dropdowns are not supported here.</strong></p>
<div class="bs-example"> <div class="bs-example">
<div class="btn-group-vertical"> <div class="btn-group-vertical">
<button type="button" class="btn btn-default">Button</button> <button type="button" class="btn btn-default">Button</button>
......
This diff is collapsed.
...@@ -1147,6 +1147,8 @@ base_url: "../" ...@@ -1147,6 +1147,8 @@ base_url: "../"
<h3>Wells</h3> <h3>Wells</h3>
<label>@well-bg</label> <label>@well-bg</label>
<input type="text" class="form-control" placeholder="#f5f5f5" data-var="@well-bg"> <input type="text" class="form-control" placeholder="#f5f5f5" data-var="@well-bg">
<label>@well-border</label>
<input type="text" class="form-control" placeholder="darken(@well-bg, 7%)" data-var="@well-border">
<h2 id="variables-accordion">Accordion</h2> <h2 id="variables-accordion">Accordion</h2>
...@@ -1247,6 +1249,11 @@ base_url: "../" ...@@ -1247,6 +1249,11 @@ base_url: "../"
<input type="text" class="form-control" placeholder="#000" data-var="@modal-backdrop-bg"> <input type="text" class="form-control" placeholder="#000" data-var="@modal-backdrop-bg">
<p class="help-block">Modal backdrop background color</p> <p class="help-block">Modal backdrop background color</p>
</div> </div>
<div class="col-md-4">
<label>@modal-backdrop-opacity</label>
<input type="text" class="form-control" placeholder=".5" data-var="@modal-backdrop-opacity">
<p class="help-block">Modal backdrop opacity</p>
</div>
</div> </div>
<h3>Modal header and footer</h3> <h3>Modal header and footer</h3>
......
This diff is collapsed.
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -982,7 +982,6 @@ if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery" ...@@ -982,7 +982,6 @@ if (typeof jQuery === "undefined") { throw new Error("Bootstrap requires jQuery"
} }
Modal.prototype.backdrop = function (callback) { Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : '' var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) { if (this.isShown && this.options.backdrop) {
......
...@@ -597,6 +597,35 @@ h1[id] { ...@@ -597,6 +597,35 @@ h1[id] {
} }
/*
* Color swatches
*
* Color swatches and associated values for our grayscale and brand colors.
*/
.color-swatches {
margin: 0 -5px;
overflow: hidden; /* clearfix */
}
.color-swatch {
float: left;
width: 100px;
height: 100px;
margin: 0 5px;
border-radius: 3px;
}
.color-swatches .gray-darker { background-color: #222; }
.color-swatches .gray-dark { background-color: #333; }
.color-swatches .gray { background-color: #555; }
.color-swatches .gray-light { background-color: #999; }
.color-swatches .gray-lighter { background-color: #eee; }
.color-swatches .brand-primary { background-color: #428bca; }
.color-swatches .brand-success { background-color: #5cb85c; }
.color-swatches .brand-warning { background-color: #f0ad4e; }
.color-swatches .brand-danger { background-color: #d9534f; }
.color-swatches .brand-info { background-color: #5bc0de; }
/* /*
* Team members * Team members
* *
...@@ -730,7 +759,7 @@ h1[id] { ...@@ -730,7 +759,7 @@ h1[id] {
} }
/* Typography */ /* Typography */
.bs-example-type .table .info { .bs-example-type .table .type-info {
color: #999; color: #999;
vertical-align: middle; vertical-align: middle;
} }
...@@ -750,6 +779,11 @@ h1[id] { ...@@ -750,6 +779,11 @@ h1[id] {
margin: 0; margin: 0;
} }
/* Contextual background colors */
.bs-example-bg-classes p {
padding: 15px;
}
/* Images */ /* Images */
.bs-example > .img-circle, .bs-example > .img-circle,
.bs-example > .img-rounded, .bs-example > .img-rounded,
...@@ -990,6 +1024,7 @@ h1[id] { ...@@ -990,6 +1024,7 @@ h1[id] {
margin-bottom: 10px; margin-bottom: 10px;
} }
.responsive-utilities-test span { .responsive-utilities-test span {
display: block;
padding: 15px 10px; padding: 15px 10px;
font-size: 14px; font-size: 14px;
font-weight: bold; font-weight: bold;
......
This diff is collapsed.
...@@ -223,9 +223,9 @@ $('#myModal').on('show.bs.modal', function (e) { ...@@ -223,9 +223,9 @@ $('#myModal').on('show.bs.modal', function (e) {
<button type="button" class="btn btn-default" data-dismiss="modal">Close</button> <button type="button" class="btn btn-default" data-dismiss="modal">Close</button>
<button type="button" class="btn btn-primary">Save changes</button> <button type="button" class="btn btn-primary">Save changes</button>
</div> </div>
</div><!-- /.modal-content --> </div>
</div><!-- /.modal-dialog --> </div>
</div><!-- /.modal --> </div>
{% endhighlight %} {% endhighlight %}
...@@ -235,6 +235,67 @@ $('#myModal').on('show.bs.modal', function (e) { ...@@ -235,6 +235,67 @@ $('#myModal').on('show.bs.modal', function (e) {
<p>Additionally, you may give a description of your modal dialog with <code>aria-describedby</code> on <code>.modal</code>.</p> <p>Additionally, you may give a description of your modal dialog with <code>aria-describedby</code> on <code>.modal</code>.</p>
</div> </div>
<h2 id="modals-sizes">Optional sizes</h2>
<p>Modals have two optional sizes, available via modifier classes to be placed on a <code>.modal-dialog</code>.</p>
<div class="bs-example">
<button class="btn btn-primary" data-toggle="modal" data-target=".bs-modal-lg">Large modal</button>
<button class="btn btn-primary" data-toggle="modal" data-target=".bs-modal-sm">Small modal</button>
</div>
{% highlight html %}
<!-- Large modal -->
<button class="btn btn-primary" data-toggle="modal" data-target=".bs-modal-lg">Large modal</button>
<div class="modal fade bs-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
...
</div>
</div>
</div>
<!-- Small modal -->
<button class="btn btn-primary" data-toggle="modal" data-target=".bs-modal-sm">Small modal</button>
<div class="modal fade bs-modal-sm" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
...
</div>
</div>
</div>
{% endhighlight %}
<!-- Modal content for the above example -->
<div class="modal fade bs-modal-lg" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-lg">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Large modal</h4>
</div>
<div class="modal-body">
...
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<div class="modal fade bs-modal-sm" tabindex="-1" role="dialog" aria-labelledby="myModalLabel" aria-hidden="true">
<div class="modal-dialog modal-sm">
<div class="modal-content">
<div class="modal-header">
<button type="button" class="close" data-dismiss="modal" aria-hidden="true">&times;</button>
<h4 class="modal-title" id="myModalLabel">Small modal</h4>
</div>
<div class="modal-body">
...
</div>
</div><!-- /.modal-content -->
</div><!-- /.modal-dialog -->
</div><!-- /.modal -->
<h2 id="modals-usage">Usage</h2> <h2 id="modals-usage">Usage</h2>
<p>The modal plugin toggles your hidden content on demand, via data attributes or JavaScript. It also adds <code>.model-open</code> to the <code>&lt;body&gt;</code> to override default scrolling behavior and generates a <code>.modal-backdrop</code> to provide a click area for dismissing shown modals when clicking outside the modal.</p> <p>The modal plugin toggles your hidden content on demand, via data attributes or JavaScript. It also adds <code>.model-open</code> to the <code>&lt;body&gt;</code> to override default scrolling behavior and generates a <code>.modal-backdrop</code> to provide a click area for dismissing shown modals when clicking outside the modal.</p>
......
{ {
"requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return"], "requireSpaceAfterKeywords": ["if", "else", "for", "while", "do", "switch", "return", "try", "catch"],
"requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true }, "requireSpacesInFunctionExpression": { "beforeOpeningCurlyBrace": true },
"disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="], "disallowLeftStickedOperators": ["?", "+", "-", "/", "*", "=", "==", "===", "!=", "!==", ">", ">=", "<", "<="],
"requireRightStickedOperators": ["!"], "requireRightStickedOperators": ["!"],
......
...@@ -149,7 +149,6 @@ ...@@ -149,7 +149,6 @@
} }
Modal.prototype.backdrop = function (callback) { Modal.prototype.backdrop = function (callback) {
var that = this
var animate = this.$element.hasClass('fade') ? 'fade' : '' var animate = this.$element.hasClass('fade') ? 'fade' : ''
if (this.isShown && this.options.backdrop) { if (this.isShown && this.options.backdrop) {
......
...@@ -37,18 +37,17 @@ ...@@ -37,18 +37,17 @@
// Optional: Group multiple button groups together for a toolbar // Optional: Group multiple button groups together for a toolbar
.btn-toolbar { .btn-toolbar {
margin-left: -5px; // Offset the first child's margin
&:extend(.clearfix all); &:extend(.clearfix all);
.btn-group { .btn-group,
.input-group {
float: left; float: left;
} }
// Space out series of button groups
> .btn, > .btn,
> .btn-group { > .btn-group,
+ .btn, > .input-group {
+ .btn-group { margin-left: 5px;
margin-left: 5px;
}
} }
} }
......
...@@ -46,6 +46,8 @@ ...@@ -46,6 +46,8 @@
background-clip: padding-box; background-clip: padding-box;
// Aligns the dropdown menu to right // Aligns the dropdown menu to right
//
// Deprecated as of 3.1 in favor of `.dropdown-menu-[dir]`
&.pull-right { &.pull-right {
right: 0; right: 0;
left: auto; left: auto;
...@@ -126,6 +128,25 @@ ...@@ -126,6 +128,25 @@
} }
} }
// Menu positioning
//
// Add extra class to `.dropdown-menu` to flip the alignment of the dropdown
// menu with the parent.
.dropdown-menu-right {
left: auto; // Reset the default from `.dropdown-menu`
right: 0;
}
// With v3, we enabled auto-flipping if you have a dropdown within a right
// aligned nav component. To enable the undoing of that, we provide an override
// to restore the default dropdown menu alignment.
//
// This is only for left-aligning a dropdown menu within a `.navbar-right` or
// `.pull-right` nav component.
.dropdown-menu-left {
left: 0;
right: auto;
}
// Dropdown section headers // Dropdown section headers
.dropdown-header { .dropdown-header {
display: block; display: block;
...@@ -180,7 +201,12 @@ ...@@ -180,7 +201,12 @@
@media (min-width: @grid-float-breakpoint) { @media (min-width: @grid-float-breakpoint) {
.navbar-right { .navbar-right {
.dropdown-menu { .dropdown-menu {
.pull-right > .dropdown-menu(); .dropdown-menu-right();
}
// Necessary for overrides of the default right aligned menu.
// Will remove come v4 in all likelihood.
.dropdown-menu-left {
.dropdown-menu-left();
} }
} }
} }
......
...@@ -10,10 +10,10 @@ ...@@ -10,10 +10,10 @@
.container { .container {
.container-fixed(); .container-fixed();
@media (min-width: @screen-sm) { @media (min-width: @screen-sm-min) {
width: @container-sm; width: @container-sm;
} }
@media (min-width: @screen-md) { @media (min-width: @screen-md-min) {
width: @container-md; width: @container-md;
} }
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
......
...@@ -17,6 +17,11 @@ ...@@ -17,6 +17,11 @@
} }
.form-control { .form-control {
// IE9 fubars the placeholder attribute in text inputs and the arrows on
// select elements in input groups. To fix it, we float the input. Details:
// https://github.com/twbs/bootstrap/issues/11561#issuecomment-28936855
float: left;
width: 100%; width: 100%;
margin-bottom: 0; margin-bottom: 0;
} }
...@@ -121,16 +126,26 @@ ...@@ -121,16 +126,26 @@
&:last-child > .btn { &:last-child > .btn {
margin-left: -1px; margin-left: -1px;
} }
}
.input-group-btn > .btn { > .btn {
position: relative; position: relative;
// Jankily prevent input button groups from wrapping
+ .btn { &:not(:first-of-type):not(:last-of-type) {
margin-left: -4px; border-radius: 0;
} }
// Bring the "active" button to the front
&:hover, // Jankily prevent input button groups from wrapping
&:active { + .btn {
z-index: 2; margin-left: -4px;
}
+ .btn:last-of-type {
margin-left: -5px;
}
// Bring the "active" button to the front
&:hover,
&:active {
z-index: 2;
}
} }
} }
...@@ -6,19 +6,17 @@ ...@@ -6,19 +6,17 @@
.jumbotron { .jumbotron {
padding: @jumbotron-padding; padding: @jumbotron-padding;
margin-bottom: @jumbotron-padding; margin-bottom: @jumbotron-padding;
font-size: @jumbotron-font-size;
font-weight: 200;
line-height: (@line-height-base * 1.5);
color: @jumbotron-color; color: @jumbotron-color;
background-color: @jumbotron-bg; background-color: @jumbotron-bg;
h1, h1,
.h1 { .h1 {
line-height: 1;
color: @jumbotron-heading-color; color: @jumbotron-heading-color;
} }
p { p {
line-height: 1.4; margin-bottom: (@jumbotron-padding / 2);
font-size: @jumbotron-font-size;
font-weight: 200;
} }
.container & { .container & {
......
...@@ -79,7 +79,7 @@ a.list-group-item { ...@@ -79,7 +79,7 @@ a.list-group-item {
color: inherit; color: inherit;
} }
.list-group-item-text { .list-group-item-text {
color: lighten(@list-group-active-bg, 40%); color: @list-group-active-text-color;
} }
} }
} }
......
...@@ -147,17 +147,17 @@ ...@@ -147,17 +147,17 @@
// Transformations // Transformations
.rotate(@degrees) { .rotate(@degrees) {
-webkit-transform: rotate(@degrees); -webkit-transform: rotate(@degrees);
-ms-transform: rotate(@degrees); // IE9+ -ms-transform: rotate(@degrees); // IE9 only
transform: rotate(@degrees); transform: rotate(@degrees);
} }
.scale(@ratio; @ratio-y...) { .scale(@ratio; @ratio-y...) {
-webkit-transform: scale(@ratio, @ratio-y); -webkit-transform: scale(@ratio, @ratio-y);
-ms-transform: scale(@ratio, @ratio-y); // IE9+ -ms-transform: scale(@ratio, @ratio-y); // IE9 only
transform: scale(@ratio, @ratio-y); transform: scale(@ratio, @ratio-y);
} }
.translate(@x; @y) { .translate(@x; @y) {
-webkit-transform: translate(@x, @y); -webkit-transform: translate(@x, @y);
-ms-transform: translate(@x, @y); // IE9+ -ms-transform: translate(@x, @y); // IE9 only
transform: translate(@x, @y); transform: translate(@x, @y);
} }
.skew(@x; @y) { .skew(@x; @y) {
...@@ -172,12 +172,12 @@ ...@@ -172,12 +172,12 @@
.rotateX(@degrees) { .rotateX(@degrees) {
-webkit-transform: rotateX(@degrees); -webkit-transform: rotateX(@degrees);
-ms-transform: rotateX(@degrees); // IE9+ -ms-transform: rotateX(@degrees); // IE9 only
transform: rotateX(@degrees); transform: rotateX(@degrees);
} }
.rotateY(@degrees) { .rotateY(@degrees) {
-webkit-transform: rotateY(@degrees); -webkit-transform: rotateY(@degrees);
-ms-transform: rotateY(@degrees); // IE9+ -ms-transform: rotateY(@degrees); // IE9 only
transform: rotateY(@degrees); transform: rotateY(@degrees);
} }
.perspective(@perspective) { .perspective(@perspective) {
...@@ -193,6 +193,7 @@ ...@@ -193,6 +193,7 @@
.transform-origin(@origin) { .transform-origin(@origin) {
-webkit-transform-origin: @origin; -webkit-transform-origin: @origin;
-moz-transform-origin: @origin; -moz-transform-origin: @origin;
-ms-transform-origin: @origin; // IE9 only
transform-origin: @origin; transform-origin: @origin;
} }
...@@ -201,6 +202,30 @@ ...@@ -201,6 +202,30 @@
-webkit-animation: @animation; -webkit-animation: @animation;
animation: @animation; animation: @animation;
} }
.animation-name(@name) {
-webkit-animation-name: @name;
animation-name: @name;
}
.animation-duration(@duration) {
-webkit-animation-duration: @duration;
animation-duration: @duration;
}
.animation-timing-function(@timing-function) {
-webkit-animation-timing-function: @timing-function;
animation-timing-function: @timing-function;
}
.animation-delay(@delay) {
-webkit-animation-delay: @delay;
animation-delay: @delay;
}
.animation-iteration-count(@iteration-count) {
-webkit-animation-iteration-count: @iteration-count;
animation-iteration-count: @iteration-count;
}
.animation-direction(@direction) {
-webkit-animation-direction: @direction;
animation-direction: @direction;
}
// Backface visibility // Backface visibility
// Prevent browsers from flickering when using CSS 3D transforms. // Prevent browsers from flickering when using CSS 3D transforms.
...@@ -353,7 +378,7 @@ ...@@ -353,7 +378,7 @@
// //
// Keep images from scaling beyond the width of their parents. // Keep images from scaling beyond the width of their parents.
.img-responsive(@display: block;) { .img-responsive(@display: block) {
display: @display; display: @display;
max-width: 100%; // Part 1: Set a maximum relative to the parent max-width: 100%; // Part 1: Set a maximum relative to the parent
height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching height: auto; // Part 2: Scale the height according to the width, otherwise you get stretching
...@@ -613,30 +638,39 @@ ...@@ -613,30 +638,39 @@
position: relative; position: relative;
float: left; float: left;
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
// Prevent columns from collapsing when empty
min-height: 1px; min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2); padding-left: (@gutter / 2);
padding-right: (@gutter / 2); padding-right: (@gutter / 2);
} }
.make-xs-column-offset(@columns) {
@media (min-width: @screen-xs-min) {
margin-left: percentage((@columns / @grid-columns));
}
}
.make-xs-column-push(@columns) {
@media (min-width: @screen-xs-min) {
left: percentage((@columns / @grid-columns));
}
}
.make-xs-column-pull(@columns) {
@media (min-width: @screen-xs-min) {
right: percentage((@columns / @grid-columns));
}
}
// Generate the small columns // Generate the small columns
.make-sm-column(@columns; @gutter: @grid-gutter-width) { .make-sm-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
// Prevent columns from collapsing when empty
min-height: 1px; min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2); padding-left: (@gutter / 2);
padding-right: (@gutter / 2); padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
float: left; float: left;
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
// Generate the small column offsets
.make-sm-column-offset(@columns) { .make-sm-column-offset(@columns) {
@media (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
...@@ -653,23 +687,19 @@ ...@@ -653,23 +687,19 @@
} }
} }
// Generate the medium columns // Generate the medium columns
.make-md-column(@columns; @gutter: @grid-gutter-width) { .make-md-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
// Prevent columns from collapsing when empty
min-height: 1px; min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2); padding-left: (@gutter / 2);
padding-right: (@gutter / 2); padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
float: left; float: left;
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
// Generate the medium column offsets
.make-md-column-offset(@columns) { .make-md-column-offset(@columns) {
@media (min-width: @screen-md-min) { @media (min-width: @screen-md-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
...@@ -686,23 +716,19 @@ ...@@ -686,23 +716,19 @@
} }
} }
// Generate the large columns // Generate the large columns
.make-lg-column(@columns; @gutter: @grid-gutter-width) { .make-lg-column(@columns; @gutter: @grid-gutter-width) {
position: relative; position: relative;
// Prevent columns from collapsing when empty
min-height: 1px; min-height: 1px;
// Inner gutter via padding
padding-left: (@gutter / 2); padding-left: (@gutter / 2);
padding-right: (@gutter / 2); padding-right: (@gutter / 2);
// Calculate width based on number of columns available
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
float: left; float: left;
width: percentage((@columns / @grid-columns)); width: percentage((@columns / @grid-columns));
} }
} }
// Generate the large column offsets
.make-lg-column-offset(@columns) { .make-lg-column-offset(@columns) {
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
margin-left: percentage((@columns / @grid-columns)); margin-left: percentage((@columns / @grid-columns));
......
...@@ -22,7 +22,11 @@ ...@@ -22,7 +22,11 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: @zindex-modal-background; z-index: @zindex-modal;
// Prevent Chrome on Windows from adding a focus outline. For details, see
// https://github.com/twbs/bootstrap/pull/10951.
outline: 0;
// When fading in the modal, animate it to slide down // When fading in the modal, animate it to slide down
&.fade .modal-dialog { &.fade .modal-dialog {
...@@ -37,7 +41,6 @@ ...@@ -37,7 +41,6 @@
position: relative; position: relative;
width: auto; width: auto;
margin: 10px; margin: 10px;
z-index: (@zindex-modal-background + 10);
} }
// Actual modal // Actual modal
...@@ -60,11 +63,11 @@ ...@@ -60,11 +63,11 @@
right: 0; right: 0;
bottom: 0; bottom: 0;
left: 0; left: 0;
z-index: (@zindex-modal-background - 10); z-index: @zindex-modal-background;
background-color: @modal-backdrop-bg; background-color: @modal-backdrop-bg;
// Fade for backdrop // Fade for backdrop
&.fade { .opacity(0); } &.fade { .opacity(0); }
&.in { .opacity(.5); } &.in { .opacity(@modal-backdrop-opacity); }
} }
// Modal header // Modal header
...@@ -116,8 +119,9 @@ ...@@ -116,8 +119,9 @@
} }
// Scale up the modal // Scale up the modal
@media screen and (min-width: @screen-sm-min) { @media (min-width: @screen-sm-min) {
// Automatically set modal's width for larger viewports
.modal-dialog { .modal-dialog {
width: 600px; width: 600px;
margin: 30px auto; margin: 30px auto;
...@@ -126,4 +130,8 @@ ...@@ -126,4 +130,8 @@
.box-shadow(0 5px 15px rgba(0,0,0,.5)); .box-shadow(0 5px 15px rgba(0,0,0,.5));
} }
// Modal sizes
.modal-sm { width: @modal-sm; }
.modal-lg { width: @modal-lg; }
} }
...@@ -161,6 +161,13 @@ ...@@ -161,6 +161,13 @@
text-decoration: none; text-decoration: none;
} }
// Prevent Glyphicons from increasing height of navbar
> .glyphicon {
float: left;
margin-top: -2px;
margin-right: 5px;
}
@media (min-width: @grid-float-breakpoint) { @media (min-width: @grid-float-breakpoint) {
.navbar > .container &, .navbar > .container &,
.navbar > .container-fluid & { .navbar > .container-fluid & {
...@@ -329,13 +336,6 @@ ...@@ -329,13 +336,6 @@
.border-bottom-radius(0); .border-bottom-radius(0);
} }
// Right aligned menus need alt position
.navbar-nav.pull-right > li > .dropdown-menu,
.navbar-nav > li > .dropdown-menu.pull-right {
left: auto;
right: 0;
}
// Buttons in navbars // Buttons in navbars
// //
......
...@@ -14,7 +14,7 @@ ...@@ -14,7 +14,7 @@
// Panel contents // Panel contents
.panel-body { .panel-body {
padding: 15px; padding: @panel-body-padding;
&:extend(.clearfix all); &:extend(.clearfix all);
} }
......
...@@ -23,172 +23,51 @@ ...@@ -23,172 +23,51 @@
// Visibility utilities // Visibility utilities
.visible-xs { .visible-xs {
.responsive-invisibility(); .responsive-invisibility();
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
.responsive-visibility(); .responsive-visibility();
} }
&.visible-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-visibility();
}
}
&.visible-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-visibility();
}
}
&.visible-lg {
@media (min-width: @screen-lg-min) {
.responsive-visibility();
}
}
} }
.visible-sm { .visible-sm {
.responsive-invisibility(); .responsive-invisibility();
&.visible-xs {
@media (max-width: @screen-xs-max) {
.responsive-visibility();
}
}
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-visibility(); .responsive-visibility();
} }
&.visible-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-visibility();
}
}
&.visible-lg {
@media (min-width: @screen-lg-min) {
.responsive-visibility();
}
}
} }
.visible-md { .visible-md {
.responsive-invisibility(); .responsive-invisibility();
&.visible-xs {
@media (max-width: @screen-xs-max) {
.responsive-visibility();
}
}
&.visible-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-visibility();
}
}
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) { @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-visibility(); .responsive-visibility();
} }
&.visible-lg {
@media (min-width: @screen-lg-min) {
.responsive-visibility();
}
}
} }
.visible-lg { .visible-lg {
.responsive-invisibility(); .responsive-invisibility();
&.visible-xs {
@media (max-width: @screen-xs-max) {
.responsive-visibility();
}
}
&.visible-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-visibility();
}
}
&.visible-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-visibility();
}
}
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
.responsive-visibility(); .responsive-visibility();
} }
} }
.hidden-xs { .hidden-xs {
.responsive-visibility();
@media (max-width: @screen-xs-max) { @media (max-width: @screen-xs-max) {
.responsive-invisibility(); .responsive-invisibility();
} }
&.hidden-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-invisibility();
}
}
&.hidden-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-invisibility();
}
}
&.hidden-lg {
@media (min-width: @screen-lg-min) {
.responsive-invisibility();
}
}
} }
.hidden-sm { .hidden-sm {
.responsive-visibility();
&.hidden-xs {
@media (max-width: @screen-xs-max) {
.responsive-invisibility();
}
}
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) { @media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-invisibility(); .responsive-invisibility();
} }
&.hidden-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-invisibility();
}
}
&.hidden-lg {
@media (min-width: @screen-lg-min) {
.responsive-invisibility();
}
}
} }
.hidden-md { .hidden-md {
.responsive-visibility();
&.hidden-xs {
@media (max-width: @screen-xs-max) {
.responsive-invisibility();
}
}
&.hidden-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-invisibility();
}
}
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) { @media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-invisibility(); .responsive-invisibility();
} }
&.hidden-lg {
@media (min-width: @screen-lg-min) {
.responsive-invisibility();
}
}
} }
.hidden-lg { .hidden-lg {
.responsive-visibility();
&.hidden-xs {
@media (max-width: @screen-xs-max) {
.responsive-invisibility();
}
}
&.hidden-sm {
@media (min-width: @screen-sm-min) and (max-width: @screen-sm-max) {
.responsive-invisibility();
}
}
&.hidden-md {
@media (min-width: @screen-md-min) and (max-width: @screen-md-max) {
.responsive-invisibility();
}
}
@media (min-width: @screen-lg-min) { @media (min-width: @screen-lg-min) {
.responsive-invisibility(); .responsive-invisibility();
} }
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
line-height: 1.4; line-height: 1.4;
.opacity(0); .opacity(0);
&.in { .opacity(.9); } &.in { .opacity(@tooltip-opacity); }
&.top { margin-top: -3px; padding: @tooltip-arrow-width 0; } &.top { margin-top: -3px; padding: @tooltip-arrow-width 0; }
&.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; } &.right { margin-left: 3px; padding: 0 @tooltip-arrow-width; }
&.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; } &.bottom { margin-top: 3px; padding: @tooltip-arrow-width 0; }
......
...@@ -81,7 +81,13 @@ small, ...@@ -81,7 +81,13 @@ small,
// Undo browser default styling // Undo browser default styling
cite { font-style: normal; } cite { font-style: normal; }
// Contextual emphasis // Alignment
.text-left { text-align: left; }
.text-right { text-align: right; }
.text-center { text-align: center; }
.text-justify { text-align: justify; }
// Contextual colors
.text-muted { .text-muted {
color: @text-muted; color: @text-muted;
} }
...@@ -116,11 +122,42 @@ cite { font-style: normal; } ...@@ -116,11 +122,42 @@ cite { font-style: normal; }
} }
} }
// Alignment // Contextual backgrounds
.text-left { text-align: left; } // For now we'll leave these alongside the text classes until v4 when we can
.text-right { text-align: right; } // safely shift things around (per SemVer rules).
.text-center { text-align: center; } .bg-primary {
.text-justify { text-align: justify; } // Given the contrast here, this is the only class to have it's color inverted
// automatically.
color: #fff;
background-color: @brand-primary;
a&:hover {
background-color: darken(@brand-primary, 10%);
}
}
.bg-warning {
background-color: @state-warning-bg;
a&:hover {
background-color: darken(@state-warning-bg, 10%);
}
}
.bg-danger {
background-color: @state-danger-bg;
a&:hover {
background-color: darken(@state-danger-bg, 10%);
}
}
.bg-success {
background-color: @state-success-bg;
a&:hover {
background-color: darken(@state-success-bg, 10%);
}
}
.bg-info {
background-color: @state-info-bg;
a&:hover {
background-color: darken(@state-info-bg, 10%);
}
}
// Page header // Page header
......
...@@ -189,6 +189,9 @@ ...@@ -189,6 +189,9 @@
@dropdown-header-color: @gray-light; @dropdown-header-color: @gray-light;
// Note: Deprecated @dropdown-caret-color as of v3.1.0
@dropdown-caret-color: #000;
// COMPONENT VARIABLES // COMPONENT VARIABLES
// -------------------------------------------------- // --------------------------------------------------
...@@ -402,6 +405,7 @@ ...@@ -402,6 +405,7 @@
@tooltip-max-width: 200px; @tooltip-max-width: 200px;
@tooltip-color: #fff; @tooltip-color: #fff;
@tooltip-bg: #000; @tooltip-bg: #000;
@tooltip-opacity: .9;
@tooltip-arrow-width: 5px; @tooltip-arrow-width: 5px;
@tooltip-arrow-color: @tooltip-bg; @tooltip-arrow-color: @tooltip-bg;
...@@ -450,9 +454,13 @@ ...@@ -450,9 +454,13 @@
@modal-content-fallback-border-color: #999; @modal-content-fallback-border-color: #999;
@modal-backdrop-bg: #000; @modal-backdrop-bg: #000;
@modal-backdrop-opacity: .5;
@modal-header-border-color: #e5e5e5; @modal-header-border-color: #e5e5e5;
@modal-footer-border-color: @modal-header-border-color; @modal-footer-border-color: @modal-header-border-color;
@modal-lg: 900px;
@modal-sm: 300px;
// Alerts // Alerts
// ------------------------- // -------------------------
...@@ -491,24 +499,26 @@ ...@@ -491,24 +499,26 @@
// List group // List group
// ------------------------- // -------------------------
@list-group-bg: #fff; @list-group-bg: #fff;
@list-group-border: #ddd; @list-group-border: #ddd;
@list-group-border-radius: @border-radius-base; @list-group-border-radius: @border-radius-base;
@list-group-hover-bg: #f5f5f5;
@list-group-active-color: @component-active-color;
@list-group-active-bg: @component-active-bg;
@list-group-active-border: @list-group-active-bg;
@list-group-link-color: #555; @list-group-hover-bg: #f5f5f5;
@list-group-link-heading-color: #333; @list-group-active-color: @component-active-color;
@list-group-active-bg: @component-active-bg;
@list-group-active-border: @list-group-active-bg;
@list-group-active-text-color: lighten(@list-group-active-bg, 40%);
@list-group-link-color: #555;
@list-group-link-heading-color: #333;
// Panels // Panels
// ------------------------- // -------------------------
@panel-bg: #fff; @panel-bg: #fff;
@panel-inner-border: #ddd; @panel-body-padding: 15px;
@panel-border-radius: @border-radius-base; @panel-border-radius: @border-radius-base;
@panel-inner-border: #ddd;
@panel-footer-bg: #f5f5f5; @panel-footer-bg: #f5f5f5;
@panel-default-text: @gray-dark; @panel-default-text: @gray-dark;
...@@ -550,6 +560,7 @@ ...@@ -550,6 +560,7 @@
// Wells // Wells
// ------------------------- // -------------------------
@well-bg: #f5f5f5; @well-bg: #f5f5f5;
@well-border: darken(@well-bg, 7%);
// Badges // Badges
......
...@@ -9,7 +9,7 @@ ...@@ -9,7 +9,7 @@
padding: 19px; padding: 19px;
margin-bottom: 20px; margin-bottom: 20px;
background-color: @well-bg; background-color: @well-bg;
border: 1px solid darken(@well-bg, 7%); border: 1px solid @well-border;
border-radius: @border-radius-base; border-radius: @border-radius-base;
.box-shadow(inset 0 1px 1px rgba(0,0,0,.05)); .box-shadow(inset 0 1px 1px rgba(0,0,0,.05));
blockquote { blockquote {
......
{ {
"name": "bootstrap" "name": "bootstrap",
, "description": "Sleek, intuitive, and powerful front-end framework for faster and easier web development." "description": "Sleek, intuitive, and powerful front-end framework for faster and easier web development.",
, "version": "3.0.3" "version": "3.0.3",
, "keywords": ["bootstrap", "css"] "keywords": ["bootstrap", "css"],
, "homepage": "http://getbootstrap.com" "homepage": "http://getbootstrap.com",
, "author": "Twitter, Inc." "author": "Twitter, Inc.",
, "scripts": { "test": "grunt test" } "scripts": { "test": "grunt test" },
, "repository": { "repository": {
"type": "git" "type": "git"
, "url": "https://github.com/twbs/bootstrap.git" , "url": "https://github.com/twbs/bootstrap.git"
} },
, "bugs": { "bugs": {
"url": "https://github.com/twbs/bootstrap/issues" "url": "https://github.com/twbs/bootstrap/issues"
} },
, "licenses": [ "licenses": [
{ {
"type": "Apache-2.0" "type": "Apache-2.0",
, "url": "http://www.apache.org/licenses/LICENSE-2.0" "url": "http://www.apache.org/licenses/LICENSE-2.0"
}
]
, "devDependencies": {
"btoa": "~1.1.1"
, "grunt": "~0.4.1"
, "grunt-banner": "~0.2.0"
, "grunt-contrib-clean": "~0.5.0"
, "grunt-contrib-concat": "~0.3.0"
, "grunt-contrib-connect": "~0.5.0"
, "grunt-contrib-copy": "~0.4.1"
, "grunt-contrib-csslint": "~0.2.0"
, "grunt-contrib-jshint": "~0.7.0"
, "grunt-contrib-less": "~0.8.0"
, "grunt-contrib-qunit": "~0.3.0"
, "grunt-contrib-uglify": "~0.2.4"
, "grunt-contrib-watch": "~0.5.3"
, "grunt-csscomb": "~1.1.0"
, "grunt-html-validation": "~0.1.6"
, "grunt-jekyll": "~0.4.0"
, "grunt-jscs-checker": "~0.2.5"
, "grunt-saucelabs": "~4.1.2"
, "grunt-sed": "~0.1.1"
, "regexp-quote": "~0.0.0"
, "load-grunt-tasks": "~0.2.0"
}
, "jspm": {
"main": "js/bootstrap"
, "directories": { "lib": "dist" }
, "shim": {
"js/bootstrap": {
"imports": "jquery"
, "exports": "$"
}
} }
, "buildConfig": { "uglify": true } ],
"devDependencies": {
"btoa": "~1.1.1",
"grunt": "~0.4.1",
"grunt-banner": "~0.2.0",
"grunt-contrib-clean": "~0.5.0",
"grunt-contrib-concat": "~0.3.0",
"grunt-contrib-connect": "~0.5.0",
"grunt-contrib-copy": "~0.4.1",
"grunt-contrib-csslint": "~0.2.0",
"grunt-contrib-jshint": "~0.7.0",
"grunt-contrib-less": "~0.8.0",
"grunt-contrib-qunit": "~0.3.0",
"grunt-contrib-uglify": "~0.2.4",
"grunt-contrib-watch": "~0.5.3",
"grunt-csscomb": "~1.1.0",
"grunt-html-validation": "~0.1.6",
"grunt-jekyll": "~0.4.0",
"grunt-jscs-checker": "~0.2.5",
"grunt-saucelabs": "~4.1.2",
"grunt-sed": "~0.1.1",
"regexp-quote": "~0.0.0",
"load-grunt-tasks": "~0.2.0"
},
"jspm": {
"main": "js/bootstrap",
"directories": { "lib": "dist" },
"shim": {
"js/bootstrap": {
"imports": "jquery",
"exports": "$"
}
},
"buildConfig": { "uglify": true }
} }
} }
#!/usr/bin/env python2.7
from __future__ import absolute_import, unicode_literals, print_function, division
from sys import argv
from os import environ, stat, remove as _delete_file
from os.path import isfile
from hashlib import sha256
from subprocess import check_call as run
from boto.s3.connection import S3Connection
from boto.s3.key import Key
from boto.exception import S3ResponseError
NODE_MODULES_TARBALL = 'node_modules.tar.gz'
NEED_TO_UPLOAD_MARKER = '.need-to-upload'
BYTES_PER_MB = 1024 * 1024
try:
BUCKET_NAME = environ['TWBS_S3_BUCKET']
except KeyError:
raise SystemExit("TWBS_S3_BUCKET environment variable not set!")
def _sha256_of_file(filename):
hasher = sha256()
with open(filename, 'rb') as input_file:
hasher.update(input_file.read())
return hasher.hexdigest()
def _delete_file_quietly(filename):
try:
_delete_file(filename)
except (OSError, IOError):
pass
def _tarball_size():
kib = stat(NODE_MODULES_TARBALL).st_size // BYTES_PER_MB
return "{} MiB".format(kib)
if __name__ == '__main__':
# Uses environment variables:
# AWS_ACCESS_KEY_ID - AWS Access Key ID
# AWS_SECRET_ACCESS_KEY - AWS Secret Access Key
argv.pop(0)
if len(argv) != 1:
raise SystemExit("USAGE: node_modules_cache.py <download | upload>")
mode = argv.pop()
conn = S3Connection()
bucket = conn.lookup(BUCKET_NAME)
if bucket is None:
raise SystemExit("Could not access bucket!")
package_json_hash = _sha256_of_file('package.json')
print('sha256(package.json) = ' + package_json_hash)
key = Key(bucket, package_json_hash)
key.storage_class = 'REDUCED_REDUNDANCY'
if mode == 'download':
_delete_file_quietly(NEED_TO_UPLOAD_MARKER)
try:
print("Downloading tarball from S3...")
key.get_contents_to_filename(NODE_MODULES_TARBALL)
except S3ResponseError as err:
open(NEED_TO_UPLOAD_MARKER, 'a').close()
print(err)
raise SystemExit("Cached node_modules download failed!")
print("Downloaded {}.".format(_tarball_size()))
print("Extracting tarball...")
run(['tar', 'xzf', NODE_MODULES_TARBALL])
print("node_modules successfully installed from cache.")
elif mode == 'upload':
if isfile(NEED_TO_UPLOAD_MARKER):
print("Creating tarball...")
run(['tar', 'czf', NODE_MODULES_TARBALL, 'node_modules'])
print("Uploading tarball to S3... ({})".format(_tarball_size()))
key.set_contents_from_filename(NODE_MODULES_TARBALL)
print("node_modules cache successfully updated.")
_delete_file_quietly(NEED_TO_UPLOAD_MARKER)
else:
print("No need to upload anything.")
else:
raise SystemExit("Unrecognized mode {!r}".format(mode))
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment