diff --git a/assets/images/docs.png b/assets/images/docs.png
index f15d45ab..d1161c88 100644
Binary files a/assets/images/docs.png and b/assets/images/docs.png differ
diff --git a/assets/images/docs@2x.png b/assets/images/docs@2x.png
index 3ca913d2..6ccbcde5 100644
Binary files a/assets/images/docs@2x.png and b/assets/images/docs@2x.png differ
diff --git a/assets/javascripts/news.json b/assets/javascripts/news.json
index 6b365355..5eb4e159 100644
--- a/assets/javascripts/news.json
+++ b/assets/javascripts/news.json
@@ -1,5 +1,8 @@
[
[
+ "2016-07-31",
+ "New documentation: Bootstrap"
+ ], [
"2016-07-24",
"New documentations: Julia, Crystal and Redux"
], [
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index cbf8d811..9638ee52 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -105,6 +105,11 @@ credits = [
'2010-2016 Jeremy Ashkenas, DocumentCloud',
'MIT',
'https://raw.githubusercontent.com/jashkenas/backbone/master/LICENSE'
+ ], [
+ 'Bootstrap',
+ '2011-2016 Twitter, Inc.',
+ 'CC BY',
+ 'https://creativecommons.org/licenses/by/3.0/'
], [
'Bower',
'2016 Bower contributors',
diff --git a/assets/javascripts/views/pages/simple.coffee b/assets/javascripts/views/pages/simple.coffee
index 84636982..6675e9b1 100644
--- a/assets/javascripts/views/pages/simple.coffee
+++ b/assets/javascripts/views/pages/simple.coffee
@@ -8,6 +8,7 @@ class app.views.SimplePage extends app.views.BasePage
app.views.AngularPage =
app.views.AngularjsPage =
+app.views.Bsv3Page =
app.views.CakephpPage =
app.views.ChaiPage =
app.views.CrystalPage =
diff --git a/assets/stylesheets/application-dark.css.scss b/assets/stylesheets/application-dark.css.scss
index cb86c364..ba6084a0 100644
--- a/assets/stylesheets/application-dark.css.scss
+++ b/assets/stylesheets/application-dark.css.scss
@@ -34,6 +34,7 @@
'pages/angular',
'pages/angularjs',
'pages/apache',
+ 'pages/bootstrap',
'pages/bower',
'pages/c',
'pages/cakephp',
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index 1cc9d588..bce9f41d 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -34,6 +34,7 @@
'pages/angular',
'pages/angularjs',
'pages/apache',
+ 'pages/bootstrap',
'pages/bower',
'pages/c',
'pages/cakephp',
diff --git a/assets/stylesheets/global/_icons.scss b/assets/stylesheets/global/_icons.scss
index 2c04cd87..56a2b589 100644
--- a/assets/stylesheets/global/_icons.scss
+++ b/assets/stylesheets/global/_icons.scss
@@ -133,6 +133,7 @@
._icon-crystal:before { background-position: -6rem -8rem; @extend %darkIconFix !optional; }
._icon-julia:before { background-position: -7rem -8rem; @extend %darkIconFix !optional; }
._icon-redux:before { background-position: -8rem -8rem; @extend %darkIconFix !optional; }
+._icon-bootstrap:before { background-position: -9rem -8rem; }
._icon-react_native:before { background-position: 0 -9rem; }
._icon-phalcon:before { background-position: -1rem -9rem; }
._icon-matplotlib:before { background-position: -2rem -9rem; }
diff --git a/assets/stylesheets/pages/_bootstrap.scss b/assets/stylesheets/pages/_bootstrap.scss
new file mode 100644
index 00000000..8f89d167
--- /dev/null
+++ b/assets/stylesheets/pages/_bootstrap.scss
@@ -0,0 +1,62 @@
+._bsv3 {
+ @extend %simple;
+
+ h4 > code, h5 > code { @extend %label; }
+
+ h2 > small {
+ color: $textColorLight;
+ float: right;
+ }
+
+ .bs-callout { @extend %note; }
+ .bs-callout-info { @extend %note-blue; }
+ .bs-callout-danger { @extend %note-red; }
+ .bs-callout > h4, .bs-callout > h5 { margin-top: .25rem; }
+
+ .text-danger { @extend %label, %label-red; }
+
+ .bs-example {
+ padding: .375rem .625rem;
+ line-height: 1.5;
+ @extend %heading-box;
+ }
+
+ div.bs-example {
+ border-bottom-left-radius: 0;
+ border-bottom-right-radius: 0;
+
+ + pre {
+ border-top-left-radius: 0;
+ border-top-right-radius: 0;
+ border-top: 0;
+ margin-top: 0;
+ }
+ }
+
+ a.thumbnail {
+ display: block;
+ padding: .25em;
+ @extend %box;
+
+ &:after { content: none; }
+ + h4 { margin: .75em 0 .5em; }
+ > img { display: block; }
+ }
+
+ .col { margin-bottom: 1.5em; }
+
+ @media (min-width: 800px) {
+ .row {
+ display: flex;
+ flex-wrap: wrap;
+ }
+
+ .col {
+ flex: 0 1 auto;
+ padding: 0 1em;
+ width: 33.33%;
+ -moz-box-sizing: border-box;
+ box-sizing: border-box;
+ }
+ }
+}
diff --git a/assets/stylesheets/pages/_simple.scss b/assets/stylesheets/pages/_simple.scss
index cdb8298d..128f029e 100644
--- a/assets/stylesheets/pages/_simple.scss
+++ b/assets/stylesheets/pages/_simple.scss
@@ -12,7 +12,7 @@
h1, h2, h3 { margin-left: 0; }
}
- p > code, li > code { @extend %label; }
+ p > code, li > code, td > code { @extend %label; }
blockquote { @extend %note; }
blockquote > h4, blockquote > h5 { margin-top: .25rem; }
}
diff --git a/lib/docs/filters/bootstrap/clean_html_v3.rb b/lib/docs/filters/bootstrap/clean_html_v3.rb
new file mode 100644
index 00000000..becd1e3d
--- /dev/null
+++ b/lib/docs/filters/bootstrap/clean_html_v3.rb
@@ -0,0 +1,64 @@
+module Docs
+ class Bootstrap
+ class CleanHtmlV3Filter < Filter
+ def call
+ at_css('div[role=main]').child.before(at_css('#content .container').inner_html)
+ @doc = at_css('div[role=main]')
+
+ css('h1, h2, h3, h4, h5').each do |node|
+ node.name = node.name.sub(/\d/) { |i| i.to_i + 1 }
+ end
+ at_css('h2').name = 'h1'
+
+ css('hr', '.zero-clipboard', '.modal', '.panel-group').remove
+
+ css('.bs-docs-section', '.table-responsive').each do |node|
+ node.before(node.children).remove
+ end
+
+ css('> .show-grid', '.bs-example', '.bs-glyphicons', '.responsive-utilities-test').each do |node|
+ if node.previous_element['class'].try(:include?, 'bs-example')
+ node.remove
+ else
+ node.content = ''
+ node.name = 'p'
+ node['class'] = 'bs-example'
+ node.remove_attribute('data-example-id')
+ prev = node.previous_element
+ prev = prev.previous_element until prev['id']
+ node.inner_html = %(Open example on getbootstrap.com)
+ end
+ end
+
+ css('.bs-example + figure').each do |node|
+ node.previous_element.name = 'div'
+ end
+
+ css('div[class*="col-"]').each do |node|
+ node['class'] = 'col'
+ end
+
+ css('.__cf_email__').each do |node|
+ node.replace(decode_cloudflare_email(node['data-cfemail']))
+ end
+
+ css('figure.highlight').each do |node|
+ code = node.at_css('code')
+ node['data-language'] = code['data-lang']
+ node.content = code.content
+ node.name = 'pre'
+ end
+
+ css('table, tr, td, th, pre').each do |node|
+ node.remove_attribute('class')
+ end
+
+ css('thead td:empty').each do |node|
+ node.name = 'th'
+ end
+
+ doc
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/bootstrap/entries_v3.rb b/lib/docs/filters/bootstrap/entries_v3.rb
new file mode 100644
index 00000000..85c439f8
--- /dev/null
+++ b/lib/docs/filters/bootstrap/entries_v3.rb
@@ -0,0 +1,65 @@
+module Docs
+ class Bootstrap
+ class EntriesV3Filter < Docs::EntriesFilter
+ def get_name
+ at_css('h1').content
+ end
+
+ def get_type
+ name
+ end
+
+ def additional_entries
+ return [] if root_page?
+ entries = []
+
+ css('.bs-docs-sidenav > li').each do |node|
+ link = node.at_css('a')
+ name = link.content
+ next if IGNORE_ENTRIES.include?(name)
+
+ id = link['href'].remove('#')
+ entries << [name, id]
+ next if name =~ /Sass|Less|Glyphicons/
+
+ node.css('> ul > li > a').each do |link|
+ n = link.content
+ next if n.start_with?('Ex: ') || n.start_with?('Default ') || n =~ /example/i || IGNORE_ENTRIES.include?(n)
+ id = link['href'].remove('#')
+ n.downcase!
+ n.prepend "#{name}: "
+ entries << [n, id]
+ end
+ end
+
+ %w(modals dropdowns scrollspy tabs tooltips popovers alerts buttons collapse carousel affix).each do |dom_id|
+ css("##{dom_id}-options + p + div tbody td:first-child").each do |node|
+ name = node.content.strip
+ id = node.parent['id'] = "#{dom_id}-#{name.parameterize}-option"
+ name.prepend "#{dom_id.singularize.titleize}: "
+ name << ' (option)'
+ entries << [name, id]
+ end
+
+ css("##{dom_id}-methods ~ h4 code").each do |node|
+ next unless name = node.content[/\('(\w+)'\)/, 1]
+ id = node.parent['id'] = "#{dom_id}-#{name.parameterize}-method"
+ name.prepend "#{dom_id.singularize.titleize}: "
+ name << ' (method)'
+ entries << [name, id]
+ end
+ end
+
+ entries
+ end
+
+ IGNORE_ENTRIES = %w(
+ Overview
+ Introduction
+ Usage
+ Methods
+ Options
+ )
+ end
+ end
+end
diff --git a/lib/docs/scrapers/bootstrap.rb b/lib/docs/scrapers/bootstrap.rb
new file mode 100644
index 00000000..d0c10d66
--- /dev/null
+++ b/lib/docs/scrapers/bootstrap.rb
@@ -0,0 +1,33 @@
+module Docs
+ class Bootstrap < UrlScraper
+ options[:attribution] = <<-HTML
+ © 2011–2016 Twitter, Inc.
+ Code licensed under the MIT License.
+ Documentation licensed under the Creative Commons Attribution License v3.0.
+ HTML
+
+ version '3' do
+ self.type = 'bsv3'
+ self.release = '3.3.7'
+ self.base_url = 'https://getbootstrap.com/'
+ self.root_path = 'getting-started'
+ self.links = {
+ home: 'https://getbootstrap.com/',
+ code: 'https://github.com/twbs/bootstrap'
+ }
+
+ html_filters.push 'bootstrap/entries_v3', 'bootstrap/clean_html_v3'
+
+ options[:trailing_slash] = false
+ options[:only] = %w(getting-started css components javascript)
+ end
+
+ private
+
+ def handle_response(response)
+ response.effective_url.scheme = 'https'
+ response.effective_url.path = response.effective_url.path.remove(/\/\z/)
+ super
+ end
+ end
+end
diff --git a/public/icons/docs/bootstrap/16.png b/public/icons/docs/bootstrap/16.png
new file mode 100644
index 00000000..2ac6870c
Binary files /dev/null and b/public/icons/docs/bootstrap/16.png differ
diff --git a/public/icons/docs/bootstrap/16@2x.png b/public/icons/docs/bootstrap/16@2x.png
new file mode 100644
index 00000000..86e0e66f
Binary files /dev/null and b/public/icons/docs/bootstrap/16@2x.png differ
diff --git a/public/icons/docs/bootstrap/SOURCE b/public/icons/docs/bootstrap/SOURCE
new file mode 100644
index 00000000..936a54e3
--- /dev/null
+++ b/public/icons/docs/bootstrap/SOURCE
@@ -0,0 +1 @@
+https://raw.githubusercontent.com/twbs/bootstrap/master/docs/favicon.ico