diff --git a/assets/images/icons.png b/assets/images/icons.png
index c58499ee..e6c3318b 100644
Binary files a/assets/images/icons.png and b/assets/images/icons.png differ
diff --git a/assets/images/icons@2x.png b/assets/images/icons@2x.png
index 6e2407f5..330d0858 100644
Binary files a/assets/images/icons@2x.png and b/assets/images/icons@2x.png differ
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index 24097cf2..e9e549a0 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/assets/javascripts/templates/pages/about_tmpl.coffee
@@ -128,6 +128,11 @@ credits = [
'2014 jQuery Foundation',
'MIT',
'https://raw.github.com/jquery/api.jqueryui.com/master/LICENSE-MIT.txt'
+ ], [
+ 'Knockout.js',
+ 'Steven Sanderson, the Knockout.js team, and other contributors',
+ 'MIT',
+ 'https://raw.github.com/knockout/knockout/master/LICENSE'
], [
'Less',
'2009-2014 Alexis Sellier & The Core Less Team',
diff --git a/assets/javascripts/templates/pages/news_tmpl.coffee b/assets/javascripts/templates/pages/news_tmpl.coffee
index 83068f6c..f5fcc371 100644
--- a/assets/javascripts/templates/pages/news_tmpl.coffee
+++ b/assets/javascripts/templates/pages/news_tmpl.coffee
@@ -24,8 +24,8 @@ newsItem = (date, news) ->
result
app.news = [
- [ 1390089600000, # January 19, 2013
- """ New D3.js documentation """,
+ [ 1390089600001, # January 19, 2013
+ """ New D3.js and Knockout.js documentations """,
], [
1390003200000, # January 18, 2013
""" DevDocs is now available as a Firefox web app (currently requires Aurora). """,
diff --git a/assets/javascripts/views/pages/knockout.coffee b/assets/javascripts/views/pages/knockout.coffee
new file mode 100644
index 00000000..cb23d779
--- /dev/null
+++ b/assets/javascripts/views/pages/knockout.coffee
@@ -0,0 +1,8 @@
+#= require views/pages/base
+
+class app.views.KnockoutPage extends app.views.BasePage
+ afterRender: ->
+ for el in @findAll('pre')
+ language = if el.innerHTML.indexOf('data-bind="') > 0 then 'markup' else 'javascript'
+ @highlightCode el, language
+ return
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index cba47101..de05a2e0 100644
--- a/assets/stylesheets/application.css.scss
+++ b/assets/stylesheets/application.css.scss
@@ -32,6 +32,7 @@
'pages/d3',
'pages/ember',
'pages/jquery',
+ 'pages/knockout',
'pages/git',
'pages/less',
'pages/lodash',
diff --git a/assets/stylesheets/global/_icons.scss b/assets/stylesheets/global/_icons.scss
index 21bb45f9..a1f490f2 100644
--- a/assets/stylesheets/global/_icons.scss
+++ b/assets/stylesheets/global/_icons.scss
@@ -4,7 +4,7 @@
width: 1rem;
height: 1rem;
background-image: image-url('icons.png');
- background-size: 5rem 7rem;
+ background-size: 5rem 8rem;
}
@media (-webkit-min-device-pixel-ratio: 1.5), (min-resolution: 144dpi) {
@@ -46,3 +46,4 @@
._icon-redis:before { background-position: -2rem -6rem; }
._icon-postgresql:before { background-position: -3rem -6rem; }
._icon-d3:before { background-position: -4rem -6rem; }
+._icon-knockout:before { background-position: 0 -7rem; }
diff --git a/assets/stylesheets/pages/_knockout.scss b/assets/stylesheets/pages/_knockout.scss
new file mode 100644
index 00000000..d6bd68ec
--- /dev/null
+++ b/assets/stylesheets/pages/_knockout.scss
@@ -0,0 +1,6 @@
+._knockout {
+ > h2 { @extend %block-heading; }
+ > h3 { @extend %block-label, %label-blue; }
+ p > code { @extend %label; }
+ .liveExample { @extend %note; }
+}
diff --git a/lib/docs/filters/knockout/clean_html.rb b/lib/docs/filters/knockout/clean_html.rb
new file mode 100644
index 00000000..17c1e603
--- /dev/null
+++ b/lib/docs/filters/knockout/clean_html.rb
@@ -0,0 +1,30 @@
+module Docs
+ class Knockout
+ class CleanHtmlFilter < Filter
+ def call
+ root_page? ? root : other
+
+ css('pre > code').each do |node|
+ node.before(node.children).remove
+ end
+
+ doc
+ end
+
+ def root
+ @doc = at_css '.content'
+ at_css('h1').content = 'Knockout.js'
+ end
+
+ def other
+ css('h1 ~ h1').each do |node|
+ node.name = 'h2'
+ end
+
+ css('.liveExample').each do |node|
+ node.content = 'Live examples are not available on DevDocs, sorry.'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/knockout/entries.rb b/lib/docs/filters/knockout/entries.rb
new file mode 100644
index 00000000..08db5f59
--- /dev/null
+++ b/lib/docs/filters/knockout/entries.rb
@@ -0,0 +1,38 @@
+module Docs
+ class Knockout
+ class EntriesFilter < Docs::EntriesFilter
+ NAME_BY_SLUG = {
+ 'custom-bindings' => 'Custom bindings',
+ 'custom-bindings-controlling-descendant-bindings' => 'Descendant bindings',
+ 'custom-bindings-for-virtual-elements' => 'Virtual elements',
+ 'binding-preprocessing' => 'Binding preprocessing',
+ 'json-data' => 'JSON data',
+ 'extenders' => 'Extending observables',
+ 'unobtrusive-event-handling' => 'Event handling',
+ 'fn' => 'Custom functions' }
+
+ def get_name
+ return NAME_BY_SLUG[slug] if NAME_BY_SLUG.has_key?(slug)
+ name = at_css('h1').content.strip
+ name.sub! 'The ', ''
+ name.sub! %r{"(.+?)"}, '\1'
+ name.gsub!(/ [A-Z]/) { |str| str.downcase! }
+ name
+ end
+
+ def get_type
+ if name =~ /observable/i || slug =~ /extender/
+ 'Observables'
+ elsif slug.include?('binding') && !name.end_with?('binding')
+ 'Binding'
+ elsif slug.include? 'binding'
+ 'Bindings'
+ elsif slug.include? 'plugin'
+ 'Plugins'
+ else
+ 'Miscellaneous'
+ end
+ end
+ end
+ end
+end
diff --git a/lib/docs/scrapers/knockout.rb b/lib/docs/scrapers/knockout.rb
new file mode 100644
index 00000000..941927aa
--- /dev/null
+++ b/lib/docs/scrapers/knockout.rb
@@ -0,0 +1,32 @@
+module Docs
+ class Knockout < UrlScraper
+ self.name = 'Knockout.js'
+ self.slug = 'knockout'
+ self.type = 'knockout'
+ self.version = '3.0.0'
+ self.base_url = 'http://knockoutjs.com/documentation/'
+ self.root_path = 'introduction.html'
+
+ html_filters.push 'knockout/clean_html', 'knockout/entries'
+
+ options[:follow_links] = ->(filter) { filter.root_page? }
+ options[:container] = ->(filter) { filter.root_page? ? '#wrapper' : '.content' }
+
+ options[:only] = %w(
+ json-data.html
+ extenders.html
+ throttle-extender.html
+ unobtrusive-event-handling.html
+ fn.html)
+
+ options[:only_patterns] = [
+ /observable/i,
+ /binding/,
+ /plugin/]
+
+ options[:attribution] = <<-HTML
+ © Steven Sanderson, the Knockout.js team, and other contributors
+ Licensed under the MIT License.
+ HTML
+ end
+end
diff --git a/public/icons/docs/knockout/16.png b/public/icons/docs/knockout/16.png
new file mode 100644
index 00000000..b872f2dd
Binary files /dev/null and b/public/icons/docs/knockout/16.png differ
diff --git a/public/icons/docs/knockout/16@2x.png b/public/icons/docs/knockout/16@2x.png
new file mode 100644
index 00000000..e9256a78
Binary files /dev/null and b/public/icons/docs/knockout/16@2x.png differ
diff --git a/public/icons/docs/knockout/SOURCE b/public/icons/docs/knockout/SOURCE
new file mode 100644
index 00000000..69980549
--- /dev/null
+++ b/public/icons/docs/knockout/SOURCE
@@ -0,0 +1 @@
+http://learn.knockoutjs.com/