Ver código fonte

Merge branch 'master' into octave

Jasper van Merle 6 anos atrás
pai
commit
8556e5fca8
65 arquivos alterados com 1408 adições e 10 exclusões
  1. 2 2
      .github/CONTRIBUTING.md
  2. 1 1
      assets/javascripts/lib/page.coffee
  3. 45 0
      assets/javascripts/templates/pages/about_tmpl.coffee
  4. 2 2
      assets/javascripts/views/layout/document.coffee
  5. 4 0
      assets/stylesheets/application.css.scss
  6. 21 0
      assets/stylesheets/pages/_cypress.scss
  7. 24 0
      assets/stylesheets/pages/_rxjs.scss
  8. 4 0
      assets/stylesheets/pages/_scala.scss
  9. 15 0
      assets/stylesheets/pages/_wordpress.scss
  10. 35 0
      lib/docs/filters/composer/clean_html.rb
  11. 36 0
      lib/docs/filters/composer/entries.rb
  12. 26 0
      lib/docs/filters/cypress/clean_html.rb
  13. 34 0
      lib/docs/filters/cypress/entries.rb
  14. 16 0
      lib/docs/filters/pony/clean_html.rb
  15. 35 0
      lib/docs/filters/pony/entries.rb
  16. 139 0
      lib/docs/filters/rxjs/clean_html.rb
  17. 29 0
      lib/docs/filters/rxjs/entries.rb
  18. 27 0
      lib/docs/filters/salt_stack/clean_html.rb
  19. 38 0
      lib/docs/filters/salt_stack/entries.rb
  20. 109 0
      lib/docs/filters/scala/clean_html.rb
  21. 103 0
      lib/docs/filters/scala/entries.rb
  22. 21 0
      lib/docs/filters/vue_router/clean_html.rb
  23. 71 0
      lib/docs/filters/vue_router/entries.rb
  24. 17 0
      lib/docs/filters/vuex/clean_html.rb
  25. 69 0
      lib/docs/filters/vuex/entries.rb
  26. 39 0
      lib/docs/filters/wordpress/clean_html.rb
  27. 19 0
      lib/docs/filters/wordpress/entries.rb
  28. 28 0
      lib/docs/scrapers/composer.rb
  29. 36 0
      lib/docs/scrapers/cypress.rb
  30. 27 0
      lib/docs/scrapers/pony.rb
  31. 94 0
      lib/docs/scrapers/rxjs.rb
  32. 36 0
      lib/docs/scrapers/salt_stack.rb
  33. 61 0
      lib/docs/scrapers/scala.rb
  34. 29 0
      lib/docs/scrapers/vue_router.rb
  35. 27 0
      lib/docs/scrapers/vuex.rb
  36. 44 0
      lib/docs/scrapers/wordpress.rb
  37. BIN
      public/icons/docs/composer/16.png
  38. BIN
      public/icons/docs/composer/16@2x.png
  39. 1 0
      public/icons/docs/composer/SOURCE
  40. BIN
      public/icons/docs/cypress/16.png
  41. BIN
      public/icons/docs/cypress/16@2x.png
  42. 1 0
      public/icons/docs/cypress/SOURCE
  43. BIN
      public/icons/docs/godot/16.png
  44. BIN
      public/icons/docs/godot/16@2x.png
  45. BIN
      public/icons/docs/pony/16.png
  46. BIN
      public/icons/docs/pony/16@2x.png
  47. 1 0
      public/icons/docs/pony/SOURCE
  48. BIN
      public/icons/docs/rxjs/16.png
  49. BIN
      public/icons/docs/rxjs/16@2x.png
  50. 1 0
      public/icons/docs/rxjs/SOURCE
  51. BIN
      public/icons/docs/saltstack/16.png
  52. BIN
      public/icons/docs/saltstack/16@2x.png
  53. 1 0
      public/icons/docs/saltstack/SOURCE
  54. BIN
      public/icons/docs/scala/16.png
  55. BIN
      public/icons/docs/scala/16@2x.png
  56. BIN
      public/icons/docs/vue_router/16.png
  57. BIN
      public/icons/docs/vue_router/16@2x.png
  58. 1 0
      public/icons/docs/vue_router/SOURCE
  59. BIN
      public/icons/docs/vuex/16.png
  60. BIN
      public/icons/docs/vuex/16@2x.png
  61. 1 0
      public/icons/docs/vuex/SOURCE
  62. BIN
      public/icons/docs/wordpress/16.png
  63. BIN
      public/icons/docs/wordpress/16@2x.png
  64. 1 0
      public/icons/docs/wordpress/SOURCE
  65. 37 5
      views/service-worker.js.erb

+ 2 - 2
.github/CONTRIBUTING.md

@@ -58,9 +58,9 @@ In addition to the [guidelines for contributing code](#contributing-code-and-fea
 
 ## Updating existing documentations
 
-Please don't submit a pull request updating the version number of a documentation, unless a change is required in the scraper and you've verified that it works.
+Please don't submit a pull request updating only the version number and/or the attribution of a documentation. Do submit a pull request if a bigger change is required in the scraper and you've verified that it works.
 
-To ask that an existing documentation be updated, first check the last two [documentation versions reports](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date.
+To ask that an existing documentation be updated, first check the latest [documentation versions report](https://github.com/freeCodeCamp/devdocs/issues?utf8=%E2%9C%93&q=Documentation+versions+report+is%3Aissue+author%3Adevdocs-bot+sort%3Acreated-desc). Only create an issue if the documentation has been wrongly marked as up-to-date.
 
 ## Coding conventions
 

+ 1 - 1
assets/javascripts/lib/page.coffee

@@ -214,6 +214,6 @@ track = ->
 @resetAnalytics = ->
   for cookie in document.cookie.split(/;\s?/)
     name = cookie.split('=')[0]
-    if name[0] == '_'
+    if name[0] == '_' && name[1] != '_'
       Cookies.expire(name)
   return

+ 45 - 0
assets/javascripts/templates/pages/about_tmpl.coffee

@@ -186,6 +186,11 @@ credits = [
     '2009-2018 Jeremy Ashkenas',
     'MIT',
     'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'
+  ], [
+    'Composer',
+    'Nils Adermann, Jordi Boggiano',
+    'MIT',
+    'https://raw.githubusercontent.com/composer/composer/master/LICENSE'
   ], [
     'Cordova',
     '2012-2018 The Apache Software Foundation',
@@ -201,6 +206,11 @@ credits = [
     '2012-2017 Manas Technology Solutions',
     'Apache',
     'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'
+  ], [
+    'Cypress',
+    '2017 Cypress.io',
+    'MIT',
+    'https://raw.githubusercontent.com/cypress-io/cypress-documentation/develop/LICENSE.md'
   ], [
     'D',
     '1999-2018 The D Language Foundation',
@@ -556,6 +566,11 @@ credits = [
     '2005-2017 Sebastian Bergmann',
     'CC BY',
     'https://creativecommons.org/licenses/by/3.0/'
+  ], [
+    'Pony',
+    '2016-2018, The Pony Developers & 2014-2015, Causality Ltd.',
+    'BSD',
+    'https://raw.githubusercontent.com/ponylang/ponyc/master/LICENSE'
   ], [
     'PostgreSQL',
     '1996-2018 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
@@ -631,11 +646,26 @@ credits = [
     '2010 The Rust Project Developers',
     'MIT',
     'https://raw.githubusercontent.com/rust-lang/rust/master/LICENSE-MIT'
+  ], [
+    'RxJS',
+    '2015-2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors',
+    'Apache',
+    'https://raw.githubusercontent.com/ReactiveX/rxjs/master/LICENSE.txt'
+  ], [
+    'Salt Stack',
+    '2019 SaltStack',
+    'Apache',
+    'https://raw.githubusercontent.com/saltstack/salt/develop/LICENSE'
   ], [
     'Sass',
     '2006-2016 Hampton Catlin, Nathan Weizenbaum, and Chris Eppstein',
     'MIT',
     'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE'
+  ], [
+    'Scala',
+    '2002-2019 EPFL, with contributions from Lightbend',
+    'Apache',
+    'https://raw.githubusercontent.com/scala/scala-lang/master/license.md'
   ], [
     'scikit-image',
     '2011 the scikit-image team',
@@ -706,11 +736,21 @@ credits = [
     '2010-2018 Mitchell Hashimoto',
     'MPL',
     'https://raw.githubusercontent.com/mitchellh/vagrant/master/website/LICENSE.md'
+  ], [
+    'Vue Router',
+    '2013-present Evan You',
+    'MIT',
+    'https://raw.githubusercontent.com/vuejs/vue-router/dev/LICENSE'
   ], [
     'Vue.js',
     '2013-2018 Evan You, Vue.js contributors',
     'MIT',
     'https://raw.githubusercontent.com/vuejs/vue/master/LICENSE'
+  ], [
+    'Vuex',
+    '2015-present Evan You',
+    'MIT',
+    'https://raw.githubusercontent.com/vuejs/vuex/dev/LICENSE'
   ], [
     'Vulkan',
     '2014-2017 Khronos Group Inc.<br>Vulkan and the Vulkan logo are registered trademarks of the Khronos Group Inc.',
@@ -721,6 +761,11 @@ credits = [
     'JS Foundation and other contributors',
     'CC BY',
     'https://creativecommons.org/licenses/by/4.0/'
+  ], [
+    'Wordpress',
+    '2003-2019 WordPress Foundation',
+    'GPLv2+',
+    'https://wordpress.org/about/license/'
   ], [
     'Yarn',
     '2016-present Yarn Contributors',

+ 2 - 2
assets/javascripts/views/layout/document.coffee

@@ -80,6 +80,6 @@ class app.views.Document extends app.View
       when 'reboot'             then app.reboot()
       when 'hard-reload'        then app.reload()
       when 'reset'              then app.reset() if confirm('Are you sure you want to reset DevDocs?')
-      when 'accept-analytics'   then Cookies.set('analyticsConsent', '1') && app.reboot()
-      when 'decline-analytics'  then Cookies.set('analyticsConsent', '0') && app.reboot()
+      when 'accept-analytics'   then Cookies.set('analyticsConsent', '1', expires: 1e8) && app.reboot()
+      when 'decline-analytics'  then Cookies.set('analyticsConsent', '0', expires: 1e8) && app.reboot()
     return

+ 4 - 0
assets/stylesheets/application.css.scss

@@ -45,6 +45,7 @@
         'pages/coffeescript',
         'pages/cordova',
         'pages/crystal',
+        'pages/cypress',
         'pages/d',
         'pages/d3',
         'pages/dart',
@@ -94,6 +95,8 @@
         'pages/rfc',
         'pages/rubydoc',
         'pages/rust',
+        'pages/rxjs',
+        'pages/scala',
         'pages/sinon',
         'pages/socketio',
         'pages/sphinx',
@@ -106,5 +109,6 @@
         'pages/underscore',
         'pages/vue',
         'pages/webpack',
+        'pages/wordpress',
         'pages/yard',
         'pages/yii';

+ 21 - 0
assets/stylesheets/pages/_cypress.scss

@@ -0,0 +1,21 @@
+._cypress {
+  @extend %simple;
+
+  .note {
+    h1 {
+      margin-left: inherit
+    }
+
+    &.danger {
+      @extend %note-red
+    }
+
+    &.info {
+      @extend %note-blue
+    }
+
+    &.success {
+      @extend %note-green
+    }
+  }
+}

+ 24 - 0
assets/stylesheets/pages/_rxjs.scss

@@ -0,0 +1,24 @@
+._rxjs {
+  @extend %simple;
+
+  .pre-title { @extend %pre-heading; }
+
+  .breadcrumbs { @extend %note; }
+  .banner { @extend %note-green; }
+  code.stable { @extend %label-green; }
+  code.experimental { @extend %label-orange; }
+  code.deprecated { @extend %label-red; }
+  .alert.is-important { @extend %note-red; }
+  .alert.is-helpful, .breadcrumbs { @extend %note-blue; }
+
+  .breadcrumbs { padding-left: 2em; }
+
+  img { margin: 1em 0; }
+
+  .location-badge {
+    font-style: italic;
+    text-align: right;
+  }
+
+  td h3 { margin: 0 !important; }
+}

+ 4 - 0
assets/stylesheets/pages/_scala.scss

@@ -0,0 +1,4 @@
+._scala {
+  @extend %simple;
+  .deprecated { @extend %label-red; }
+}

+ 15 - 0
assets/stylesheets/pages/_wordpress.scss

@@ -0,0 +1,15 @@
+._wordpress {
+  @extend %simple;
+
+  .breadcrumbs {
+    display: none;
+  }
+
+  .callout-warning {
+    @extend %note, %note-red;
+  }
+
+  .callout-alert {
+    @extend %note, %note-orange;
+  }
+}

+ 35 - 0
lib/docs/filters/composer/clean_html.rb

@@ -0,0 +1,35 @@
+module Docs
+  class Composer
+    class CleanHtmlFilter < Filter
+      def call
+        # Remove unneeded elements
+        css('#searchbar, .toc, .fork-and-edit, .anchor').remove
+
+        # Fix the home page titles
+        if subpath == ''
+          css('h1').each do |node|
+            node.name = 'h2'
+          end
+
+          # Add a main title before the first subtitle
+          at_css('h2').before('<h1>Composer</h1>')
+        end
+
+        # Code blocks
+        css('pre').each do |node|
+          code = node.at_css('code[class]')
+
+          unless code.nil?
+            node['data-language'] = 'javascript' if code['class'].include?('javascript')
+            node['data-language'] = 'php' if code['class'].include?('php')
+          end
+
+          node.content = node.content.strip
+          node.remove_attribute('class')
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 36 - 0
lib/docs/filters/composer/entries.rb

@@ -0,0 +1,36 @@
+module Docs
+  class Composer
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        title = at_css('h1').content
+        title = "#{Integer(subpath[1]) + 1}. #{title}" if type == 'Book'
+        title
+      end
+
+      def get_type
+        return 'Articles' if subpath.start_with?('articles/')
+        'Book'
+      end
+
+      def additional_entries
+        entries = []
+
+        if subpath == '04-schema.md' # JSON Schema
+            css('h3').each do |node|
+              name = node.content.strip
+              name.remove!(' (root-only)')
+              entries << [name, node['id'], 'JSON Schema']
+            end
+        end
+
+        if subpath == '06-config.md' # Composer config
+            css('h2').each do |node|
+              entries << [node.content.strip, node['id'], 'Configuration Options']
+            end
+        end
+
+        entries
+      end
+    end
+  end
+end

+ 26 - 0
lib/docs/filters/cypress/clean_html.rb

@@ -0,0 +1,26 @@
+# frozen_string_literal: true
+
+module Docs
+  class Cypress
+    class CleanHtmlFilter < Filter
+      def call
+        article_div = at_css('#article > div')
+        @doc = article_div unless article_div.nil?
+
+        header = at_css('h1.article-title')
+        doc.prepend_child(header) unless header.nil?
+
+        css('.article-edit-link').remove
+        css('.article-footer').remove
+        css('.article-footer-updated').remove
+
+        css('pre').each do |node|
+          node.content = node.content
+          node['data-language'] = 'javascript'
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 34 - 0
lib/docs/filters/cypress/entries.rb

@@ -0,0 +1,34 @@
+# frozen_string_literal: true
+
+module Docs
+  class Cypress
+    class EntriesFilter < Docs::EntriesFilter
+      SECTIONS = %w[
+        commands
+        core-concepts
+        cypress-api
+        events
+        getting-started
+        guides
+        overview
+        plugins
+        references
+        utilities
+      ].freeze
+
+      def get_name
+        at_css('h1.article-title').content.strip
+      end
+
+      def get_type
+        path = context[:url].path
+
+        SECTIONS.each do |section|
+          if path.match?("/#{section}/")
+            return section.split('-').map(&:capitalize).join(' ')
+          end
+        end
+      end
+    end
+  end
+end

+ 16 - 0
lib/docs/filters/pony/clean_html.rb

@@ -0,0 +1,16 @@
+module Docs
+  class Pony
+    class CleanHtmlFilter < Filter
+      def call
+        css('.headerlink').remove
+        css('hr').remove
+
+        css('pre').each do |node|
+          node.content = node.content
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 35 - 0
lib/docs/filters/pony/entries.rb

@@ -0,0 +1,35 @@
+module Docs
+  class Pony
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        title = context[:html_title].sub(/ - .*/, '').split(' ').last
+        title = "1. #{type}" if title == 'Package'
+        title
+      end
+
+      def get_type
+        subpath.split(/-([^a-z])/)[0][0..-1].sub('-', '/')
+      end
+
+      def additional_entries
+        return [] if root_page? || name.start_with?("1. ")
+
+        entries = []
+
+        css('h3').each do |node|
+          member_name = node.content
+
+          is_field = member_name.start_with?('let ')
+          member_name = member_name[4..-1] if is_field
+
+          member_name = member_name.scan(/^([a-zA-Z0-9_]+)/)[0][0]
+          member_name += '()' unless is_field
+
+          entries << ["#{name}.#{member_name}", node['id']]
+        end
+
+        entries
+      end
+    end
+  end
+end

+ 139 - 0
lib/docs/filters/rxjs/clean_html.rb

@@ -0,0 +1,139 @@
+module Docs
+  class Rxjs
+    class CleanHtmlFilter < Filter
+      def call
+        if root_page?
+          css('.card-container').remove
+          at_css('h1').content = 'RxJS Documentation'
+        end
+
+        if at_css('h1').nil?
+          title = subpath.rpartition('/').last.titleize
+          doc.prepend_child("<h1>#{title}</h1>")
+        end
+
+        css('br', 'hr', '.material-icons', '.header-link', '.breadcrumb').remove
+
+        css('.content', 'article', '.api-header', 'section', '.instance-member').each do |node|
+          node.before(node.children).remove
+        end
+
+        css('label', 'h2 > em', 'h3 > em').each do |node|
+          node.name = 'code'
+        end
+
+        css('h1 + code').each do |node|
+          node.before('<p></p>')
+          while node.next_element.name == 'code'
+            node.previous_element << ' '
+            node.previous_element << node.next_element
+          end
+          node.previous_element.prepend_child(node)
+        end
+
+        css('td h3', '.l-sub-section > h3', '.alert h3', '.row-margin > h3', '.api-heading ~ h3', '.api-heading + h2', '.metadata-member h3').each do |node|
+          node.name = 'h4'
+        end
+
+        css('.l-sub-section', '.alert', '.banner').each do |node|
+          node.name = 'blockquote'
+        end
+
+        css('.file').each do |node|
+          node.content = node.content.strip
+        end
+
+        css('.filetree .children').each do |node|
+          node.css('.file').each do |n|
+            n.content = "  #{n.content}"
+          end
+        end
+
+        css('.filetree').each do |node|
+          node.content = node.css('.file').map(&:inner_html).join("\n")
+          node.name = 'pre'
+          node.remove_attribute('class')
+        end
+
+        css('pre').each do |node|
+          node.content = node.content.strip
+
+          node['data-language'] = 'typescript' if node['path'].try(:ends_with?, '.ts')
+          node['data-language'] = 'html' if node['path'].try(:ends_with?, '.html')
+          node['data-language'] = 'css' if node['path'].try(:ends_with?, '.css')
+          node['data-language'] = 'js' if node['path'].try(:ends_with?, '.js')
+          node['data-language'] = 'json' if node['path'].try(:ends_with?, '.json')
+          node['data-language'] = node['language'].sub(/\Ats/, 'typescript').strip if node['language']
+          node['data-language'] ||= 'typescript' if node.content.start_with?('@')
+
+          node.before(%(<div class="pre-title">#{node['title']}</div>)) if node['title']
+
+          if node['class'] && node['class'].include?('api-heading')
+            node.name = 'h3'
+
+            unless node.ancestors('.instance-method').empty?
+              matches = node.inner_html.scan(/([^(& ]+)[(&]/)
+
+              unless matches.empty? || matches[0][0] == 'constructor'
+                node['name'] = matches[0][0]
+                node['id'] = node['name'].downcase + '-'
+              end
+            end
+
+            node.inner_html = "<code>#{node.inner_html}</code>"
+          end
+
+          node.remove_attribute('path')
+          node.remove_attribute('region')
+          node.remove_attribute('linenums')
+          node.remove_attribute('title')
+          node.remove_attribute('language')
+          node.remove_attribute('hidecopy')
+          node.remove_attribute('class')
+        end
+
+        css('td > .overloads').each do |node|
+          node.replace node.at_css('.detail-contents')
+        end
+
+        css('td.short-description p').each do |node|
+          signature = node.parent.parent.next_element.at_css('h3[id]')
+          signature.after(node) unless signature.nil?
+        end
+
+        css('.method-table').each do |node|
+          node.replace node.at_css('tbody')
+        end
+
+        css('.api-body > table > caption').each do |node|
+          node.name = 'center'
+          lift_out_of_table node
+        end
+
+        css('.api-body > table > tbody > tr:not([class]) > td > *').each do |node|
+          lift_out_of_table node
+        end
+
+        css('.api-body > table').each do |node|
+          node.remove if node.content.strip.blank?
+        end
+
+        css('h1[class]').remove_attr('class')
+        css('table[class]').remove_attr('class')
+        css('table[width]').remove_attr('width')
+        css('tr[style]').remove_attr('style')
+
+        css('code code').each do |node|
+          node.before(node.children).remove
+        end
+
+        doc
+      end
+
+      def lift_out_of_table(node)
+        table = node.ancestors('table').first
+        table.previous_element.after(node)
+      end
+    end
+  end
+end

+ 29 - 0
lib/docs/filters/rxjs/entries.rb

@@ -0,0 +1,29 @@
+module Docs
+  class Rxjs
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        title = at_css('h1')
+        name = title.nil? ? subpath.rpartition('/').last.titleize : title.content
+        name.prepend "#{$1}. " if subpath =~ /\-pt(\d+)/
+        name += '()' unless at_css('.api-type-label.function').nil?
+        name
+      end
+
+      def get_type
+        if slug.start_with?('guide')
+          'Guide'
+        elsif slug.start_with?('api/')
+          slug.split('/').second
+        else
+          'Miscellaneous'
+        end
+      end
+
+      def additional_entries
+        css('h3[id]').map do |node|
+          ["#{name}.#{node['name']}()", node['id']]
+        end
+      end
+    end
+  end
+end

+ 27 - 0
lib/docs/filters/salt_stack/clean_html.rb

@@ -0,0 +1,27 @@
+module Docs
+  class SaltStack
+    class CleanHtmlFilter < Filter
+      def call
+        if root_page?
+          doc.inner_html = '<h1>SaltStack</h1>'
+          return doc
+        end
+
+        css('.headerlink').remove
+
+        css('div[class^="highlight-"]').each do |node|
+          node.name = 'pre'
+          node['data-language'] = node['class'].scan(/highlight-([a-z]+)/i)[0][0]
+          node.content = node.content.strip
+        end
+
+        css('.function > dt').each do |node|
+          node.name = 'h3'
+          node.content = node.content
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 38 - 0
lib/docs/filters/salt_stack/entries.rb

@@ -0,0 +1,38 @@
+module Docs
+  class SaltStack
+    class EntriesFilter < Docs::EntriesFilter
+      SALT_REF_RGX = /salt\.([^\.]+)\.([^\s]+)/
+
+      def get_name
+        header = at_css('h1').content
+
+        ref_match = SALT_REF_RGX.match(header)
+        if ref_match
+          ns, mod = ref_match.captures
+          "#{ns}.#{mod}"
+        else
+          header
+        end
+      end
+
+      def get_type
+        slug.split('/', 3)[1]
+      end
+
+      def include_default_entry?
+        slug.split('/').last.start_with? 'salt'
+      end
+
+      def additional_entries
+        entries = []
+
+        css('.function > h3').each do |node|
+          name = node.content.remove('salt.').split('(')[0] + '()'
+          entries << [name, node['id']]
+        end
+
+        entries
+      end
+    end
+  end
+end

+ 109 - 0
lib/docs/filters/scala/clean_html.rb

@@ -0,0 +1,109 @@
+module Docs
+  class Scala
+    class CleanHtmlFilter < Filter
+      def call
+        @doc = at_css('#content')
+
+        always
+        add_title
+
+        doc
+      end
+
+      def always
+        # Remove deprecated sections
+        css('.members').each do |members|
+          header = members.at_css('h3')
+          members.remove if header.text.downcase.include? 'deprecate'
+        end
+
+        css('#mbrsel, #footer').remove
+
+        css('.diagram-container').remove
+        css('.toggleContainer > .toggle').each do |node|
+          title = node.at_css('span')
+          next if title.nil?
+
+          content = node.at_css('.hiddenContent')
+          next if content.nil?
+
+          title.name = 'dt'
+
+          content.remove_attribute('class')
+          content.remove_attribute('style')
+          content.name = 'dd'
+
+          attributes = at_css('.attributes')
+          unless attributes.nil?
+            title.parent = attributes
+            content.parent = attributes
+          end
+        end
+
+        signature = at_css('#signature')
+        signature.replace "<h2 id=\"signature\">#{signature.inner_html}</h2>"
+
+        css('div.members > h3').each do |node|
+          node.name = 'h2'
+        end
+
+        css('div.members > ol').each do |list|
+          list.css('li').each do |li|
+            h3 = doc.document.create_element 'h3'
+            h3['id'] = li['name'].rpartition('#').last unless li['name'].nil?
+
+            li.prepend_child h3
+            li.css('.shortcomment').remove
+
+            modifier = li.at_css('.modifier_kind')
+            modifier.parent = h3 unless modifier.nil?
+
+            kind = li.at_css('.modifier_kind .kind')
+            kind.content = kind.content + ' ' unless kind.nil?
+
+            symbol = li.at_css('.symbol')
+            symbol.parent = h3 unless symbol.nil?
+
+            li.swap li.children
+          end
+
+          list.swap list.children
+        end
+
+        css('.fullcomment pre, .fullcommenttop pre').each do |pre|
+          pre['data-language'] = 'scala'
+          pre.content = pre.content
+        end
+
+        # Sections of the documentation which do not seem useful
+        %w(#inheritedMembers #groupedMembers .permalink .hiddenContent .material-icons).each do |selector|
+          css(selector).remove
+        end
+
+        # Things that are not shown on the site, like deprecated members
+        css('li[visbl=prt]').remove
+      end
+
+      def add_title
+        css('.permalink').remove
+
+        definition = at_css('#definition')
+        return if definition.nil?
+
+        type_full_name = {a: 'Annotation', c: 'Class', t: 'Trait', o: 'Object', p: 'Package'}
+        type = type_full_name[definition.at_css('.big-circle').text.to_sym]
+        name = CGI.escapeHTML definition.at_css('h1').text
+
+        package = definition.at_css('#owner').text rescue ''
+        package = package + '.' unless name.empty? || package.empty?
+
+        other = definition.at_css('.morelinks').dup
+        other_content = other ? "<h3>#{other.to_html}</h3>" : ''
+
+        title_content = root_page? ? 'Package root' : "#{type} #{package}#{name}".strip
+        title = "<h1>#{title_content}</h1>"
+        definition.replace title + other_content
+      end
+    end
+  end
+end

+ 103 - 0
lib/docs/filters/scala/entries.rb

@@ -0,0 +1,103 @@
+module Docs
+  class Scala
+    class EntriesFilter < Docs::EntriesFilter
+      REPLACEMENTS = {
+        '$eq' => '=',
+        '$colon' => ':',
+        '$less' => '<',
+      }
+
+      def get_name
+        if is_package?
+          symbol = at_css('#definition h1')
+          symbol ? symbol.text.gsub(/\W+/, '') : "package"
+        else
+          name = slug.split('/').last
+
+          # Some objects have inner objects, show ParentObject$.ChildObject$ instead of ParentObject$$ChildObject$
+          name = name.gsub('$$', '$.')
+
+          REPLACEMENTS.each do |key, value|
+            name = name.gsub(key, value)
+          end
+
+          # If a dollar sign is used as separator between two characters, replace it with a dot
+          name.gsub(/([^$.])\$([^$.])/, '\1.\2')
+        end
+      end
+
+      def get_type
+        # if this entry is for a package, we group the package under the parent package
+        if is_package?
+          parent_package
+        # otherwise, group it under the regular package name
+        else
+          package_name
+        end
+      end
+
+      def include_default_entry?
+        true
+      end
+
+      def additional_entries
+        entries = []
+
+        full_name = "#{type}.#{name}".remove('$')
+        css(".members li[name^=\"#{full_name}\"]").each do |node|
+          # Ignore packages
+          kind = node.at_css('.modifier_kind > .kind')
+          next if !kind.nil? && kind.content == 'package'
+
+          # Ignore deprecated members
+          next unless node.at_css('.symbol > .name.deprecated').nil?
+
+          id = node['name'].rpartition('#').last
+          member_name = node.at_css('.name')
+
+          # Ignore members only existing of hashtags, we can't link to that
+          next if member_name.nil? || member_name.content.strip.remove('#').blank?
+
+          member = "#{name}.#{member_name.content}()"
+          entries << [member, id]
+        end
+
+        entries
+      end
+
+      private
+
+      # For the package name, we use the slug rather than parsing the package
+      # name from the HTML because companion object classes may be broken out into
+      # their own entries (by the source documentation). When that happens,
+      # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)
+      # under the package name, and not the fully-qualfied name which would
+      # include the companion object.
+      def package_name
+        name = package_drop_last(slug_parts)
+        name.empty? ? '_root_' : name
+      end
+
+      def parent_package
+        parent = package_drop_last(package_name.split('.'))
+        parent.empty? ? '_root_' : parent
+      end
+
+      def package_drop_last(parts)
+        parts[0...-1].join('.')
+      end
+
+      def slug_parts
+        slug.split('/')
+      end
+
+      def owner
+        at_css('#owner')
+      end
+
+      def is_package?
+        slug.ends_with?('index') || slug.ends_with?('package')
+      end
+    end
+  end
+end

+ 21 - 0
lib/docs/filters/vue_router/clean_html.rb

@@ -0,0 +1,21 @@
+module Docs
+  class VueRouter
+    class CleanHtmlFilter < Filter
+      def call
+        @doc = at_css('.content')
+
+        # Remove unneeded elements
+        css('.bit-sponsor, .header-anchor').remove
+
+        css('.custom-block').each do |node|
+          node.name = 'blockquote'
+
+          title = node.at_css('.custom-block-title')
+          title.name = 'strong' unless title.nil?
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 71 - 0
lib/docs/filters/vue_router/entries.rb

@@ -0,0 +1,71 @@
+module Docs
+  class VueRouter
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        name = at_css('h1').content
+        name.remove! '# '
+        name
+      end
+
+      def get_type
+        return 'Other Guides' if subpath.start_with?('guide/advanced')
+        return 'Basic Guides' if subpath.start_with?('guide') || subpath.start_with?('installation')
+        'API Reference'
+      end
+
+      def include_default_entry?
+        name != 'API Reference'
+      end
+
+      def additional_entries
+        return [] unless subpath.start_with?('api')
+
+        entries = [
+          ['<router-link>', 'router-link', 'API Reference'],
+          ['<router-view>', 'router-view', 'API Reference'],
+          ['$route', 'the-route-object', 'API Reference'],
+          ['Component Injections', 'component-injections', 'API Reference']
+        ]
+
+        css('h3').each do |node|
+          entry_name = node.content.strip
+
+          # Get the previous h2 title
+          title = node
+          title = title.previous_element until title.name == 'h2'
+          title = title.content.strip
+          title.remove! '# '
+
+          entry_name.remove! '# '
+
+          case title
+          when 'Router Construction Options'
+            entry_name = "RouterOptions.#{entry_name}"
+          when '<router-view> Props'
+            entry_name = "<router-view> `#{entry_name}` prop"
+          when '<router-link> Props'
+            entry_name = "<router-link> `#{entry_name}` prop"
+          when 'Router Instance Methods'
+            entry_name = "#{entry_name}()"
+          end
+
+          entry_name = entry_name.split(' API ')[0] if entry_name.start_with?('v-slot')
+
+          unless title == "Component Injections" || node['id'] == 'route-object-properties'
+            entries << [entry_name, node['id'], 'API Reference']
+          end
+        end
+
+        css('#route-object-properties + ul > li > p:first-child > strong').each do |node|
+          entry_name = node.content.strip
+          id = "route-object-#{entry_name.remove('$route.')}"
+
+          node['id'] = id
+          entries << [entry_name, node['id'], 'API Reference']
+        end
+
+        entries
+      end
+    end
+  end
+end

+ 17 - 0
lib/docs/filters/vuex/clean_html.rb

@@ -0,0 +1,17 @@
+module Docs
+  class Vuex
+    class CleanHtmlFilter < Filter
+      def call
+        @doc = at_css('.content')
+
+        # Remove video from root page
+        css('a[href="#"]').remove if root_page?
+
+        # Remove unneeded elements
+        css('.header-anchor').remove
+
+        doc
+      end
+    end
+  end
+end

+ 69 - 0
lib/docs/filters/vuex/entries.rb

@@ -0,0 +1,69 @@
+module Docs
+  class Vuex
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        name = at_css('h1').content
+
+        name.remove! '# '
+
+        # Add index on guides
+        unless subpath.start_with?('api')
+          sidebar_link = at_css('.sidebar-link.active')
+          all_links = css('.sidebar-link:not([href="/"]):not([href="../index"])')
+
+          index = all_links.index(sidebar_link)
+
+          name.prepend "#{index + 1}. " if index
+        end
+
+        name
+      end
+
+      def get_type
+        'Guide'
+      end
+
+      def include_default_entry?
+        name != 'API Reference'
+      end
+
+      def additional_entries
+        return [] unless subpath.start_with?('api')
+
+        entries = [
+          ['Component Binding Helpers', 'component-binding-helpers', 'API Reference'],
+          ['Store', 'vuex-store', 'API Reference'],
+        ]
+
+        css('h3').each do |node|
+          entry_name = node.content.strip
+
+          # Get the previous h2 title
+          title = node
+          title = title.previous_element until title.name == 'h2'
+          title = title.content.strip
+          title.remove! '# '
+
+          entry_name.remove! '# '
+
+          unless entry_name.start_with?('router.')
+            case title
+            when "Vuex.Store Constructor Options"
+              entry_name = "StoreOptions.#{entry_name}"
+            when "Vuex.Store Instance Properties"
+              entry_name = "Vuex.Store.#{entry_name}"
+            when "Vuex.Store Instance Methods"
+              entry_name = "Vuex.Store.#{entry_name}()"
+            when "Component Binding Helpers"
+              entry_name = "#{entry_name}()"
+            end
+          end
+
+          entries << [entry_name, node['id'], 'API Reference']
+        end
+
+        entries
+      end
+    end
+  end
+end

+ 39 - 0
lib/docs/filters/wordpress/clean_html.rb

@@ -0,0 +1,39 @@
+module Docs
+  class Wordpress
+    class CleanHtmlFilter < Filter
+      def call
+        if root_page?
+          doc.inner_html = '<h1>WordPress</h1>'
+          return doc
+        end
+
+        article = at_css('article[id^="post-"]')
+        @doc = at_css('article[id^="post-"]') unless article.nil?
+
+        css('hr', '.screen-reader-text', '.table-of-contents',
+            '.anchor', '.toc-jump', '.source-code-links', '.user-notes',
+            '.show-more', '.hide-more').remove
+
+        br = /<br\s?\/?>/i
+
+        header = at_css('h1')
+        header.content = header.content.strip
+        doc.prepend_child header
+
+        # Add PHP code highlighting
+        css('pre').each do |node|
+          node['data-language'] = 'php'
+        end
+
+        css('.source-code-container').each do |node|
+          node.name = 'pre'
+          node.inner_html = node.inner_html.gsub(br, "\n")
+          node.content = node.content.strip
+          node['data-language'] = 'php'
+        end
+
+        doc
+      end
+    end
+  end
+end

+ 19 - 0
lib/docs/filters/wordpress/entries.rb

@@ -0,0 +1,19 @@
+module Docs
+  class Wordpress
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        at_css('.breadcrumbs .trail-end').content
+      end
+
+      def get_type
+        if subpath.starts_with?('classes')
+          'Classes'
+        elsif subpath.starts_with?('hooks')
+          'Hooks'
+        elsif subpath.starts_with?('functions')
+          'Functions'
+        end
+      end
+    end
+  end
+end

+ 28 - 0
lib/docs/scrapers/composer.rb

@@ -0,0 +1,28 @@
+module Docs
+  class Composer < UrlScraper
+    self.type = 'simple'
+    self.release = '1.9.0'
+    self.base_url = 'https://getcomposer.org/doc/'
+    self.links = {
+      home: 'https://getcomposer.org',
+      code: 'https://github.com/composer/composer'
+    }
+
+    html_filters.push 'composer/clean_html', 'composer/entries'
+
+    options[:container] = '#main'
+
+    options[:skip_patterns] = [
+      /^faqs/
+    ]
+
+    options[:attribution] = <<-HTML
+      &copy; Nils Adermann, Jordi Boggiano<br>
+      Licensed under the MIT License.
+    HTML
+
+    def get_latest_version(opts)
+      get_latest_github_release('composer', 'composer', opts)
+    end
+  end
+end

+ 36 - 0
lib/docs/scrapers/cypress.rb

@@ -0,0 +1,36 @@
+# frozen_string_literal: true
+
+module Docs
+  class Cypress < UrlScraper
+    self.name = 'Cypress'
+    self.type = 'cypress'
+    self.release = '3.4.1'
+    self.base_url = 'https://docs.cypress.io'
+    self.root_path = '/api/api/table-of-contents.html'
+    self.links = {
+      home: 'https://www.cypress.io/',
+      code: 'https://github.com/cypress-io/cypress',
+    }
+
+    html_filters.push 'cypress/entries', 'cypress/clean_html'
+
+    options[:container] = '#content'
+    options[:max_image_size] = 300_000
+    options[:include_default_entry] = true
+
+    options[:skip_patterns] = [/examples\//]
+    options[:skip_link] = ->(link) {
+      href = link.attr(:href)
+      href.nil? ? true : EntriesFilter::SECTIONS.none? { |section| href.match?("/#{section}/") }
+    }
+
+    options[:attribution] = <<-HTML
+      &copy; 2017 Cypress.io<br>
+      Licensed under the MIT License.
+    HTML
+
+    def get_latest_version(opts)
+      get_latest_github_release('cypress-io', 'cypress', opts)
+    end
+  end
+end

+ 27 - 0
lib/docs/scrapers/pony.rb

@@ -0,0 +1,27 @@
+module Docs
+  class Pony < UrlScraper
+    self.type = 'simple'
+    self.release = '0.30.0'
+    self.base_url = 'https://stdlib.ponylang.io/'
+    self.links = {
+      home: 'https://www.ponylang.io/',
+      code: 'https://github.com/ponylang/ponyc'
+    }
+
+    html_filters.push 'pony/clean_html', 'pony/entries'
+
+    options[:attribution] = <<-HTML
+      &copy; 2016-2018, The Pony Developers<br>
+      &copy; 2014-2015, Causality Ltd.<br>
+      Licensed under the BSD 2-Clause License.
+    HTML
+
+    options[:container] = 'article'
+    options[:trailing_slash] = false
+    options[:skip_patterns] = [/src/, /stdlib--index/]
+
+    def get_latest_version(opts)
+      get_latest_github_release('ponylang', 'ponyc', opts)
+    end
+  end
+end

+ 94 - 0
lib/docs/scrapers/rxjs.rb

@@ -0,0 +1,94 @@
+require 'yajl/json_gem'
+
+module Docs
+  class Rxjs < UrlScraper
+    self.name = 'RxJS'
+    self.type = 'rxjs'
+    self.release = '6.5.2'
+    self.base_url = 'https://rxjs.dev/'
+    self.root_path = 'guide/overview'
+    self.links = {
+      home: 'https://rxjs.dev/',
+      code: 'https://github.com/ReactiveX/rxjs'
+    }
+
+    html_filters.push 'rxjs/clean_html', 'rxjs/entries'
+
+    options[:follow_links] = false
+    options[:only_patterns] = [/guide\//, /api\//]
+    options[:skip_patterns] = [/api\/([^\/]+)\.json/]
+    options[:fix_urls_before_parse] = ->(url) do
+      url.sub! %r{\A(\.\/)?guide/}, '/guide/'
+      url.sub! %r{\Aapi/}, '/api/'
+      url.sub! %r{\Agenerated/}, '/generated/'
+      url
+    end
+
+    options[:max_image_size] = 256_000
+
+    options[:attribution] = <<-HTML
+      &copy; 2015&ndash;2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors.<br>
+      Code licensed under an Apache-2.0 License. Documentation licensed under CC BY 4.0.
+    HTML
+
+    def get_latest_version(opts)
+      json = fetch_json('https://rxjs.dev/generated/navigation.json', opts)
+      json['__versionInfo']['raw']
+    end
+
+    private
+
+    def initial_urls
+      initial_urls = []
+
+      Request.run "#{self.class.base_url}generated/navigation.json" do |response|
+        data = JSON.parse(response.body)
+        dig = ->(entry) do
+          initial_urls << url_for("generated/docs/#{entry['url']}.json") if entry['url'] && entry['url'] != 'api'
+          entry['children'].each(&dig) if entry['children']
+        end
+        data['SideNav'].each(&dig)
+      end
+
+      Request.run "#{self.class.base_url}generated/docs/api/api-list.json" do |response|
+        data = JSON.parse(response.body)
+        dig = ->(entry) do
+          initial_urls << url_for("generated/docs/#{entry['path']}.json") if entry['path']
+          initial_urls << url_for("generated/docs/api/#{entry['name']}.json") if entry['name'] && !entry['path']
+          entry['items'].each(&dig) if entry['items']
+        end
+        data.each(&dig)
+      end
+
+      initial_urls.select do |url|
+        options[:only_patterns].any? { |pattern| url =~ pattern } &&
+          options[:skip_patterns].none? { |pattern| url =~ pattern }
+      end
+    end
+
+    def handle_response(response)
+      if response.mime_type.include?('json')
+        begin
+          response.options[:response_body] = JSON.parse(response.body)['contents']
+        rescue JSON::ParserError
+          response.options[:response_body] = ''
+        end
+        response.headers['Content-Type'] = 'text/html'
+        response.url.path = response.url.path.sub('/generated/docs/', '/').remove('.json')
+        response.effective_url.path = response.effective_url.path.sub('/generated/docs/', '/').remove('.json')
+      end
+      super
+    end
+
+    def parse(response)
+      response.body.gsub! '<code-example', '<pre'
+      response.body.gsub! '</code-example', '</pre'
+      response.body.gsub! '<code-pane', '<pre'
+      response.body.gsub! '</code-pane', '</pre'
+      response.body.gsub! '<live-example></live-example>', 'live example'
+      response.body.gsub! '<live-example', '<span'
+      response.body.gsub! '</live-example', '</span'
+      super
+    end
+  end
+end

+ 36 - 0
lib/docs/scrapers/salt_stack.rb

@@ -0,0 +1,36 @@
+module Docs
+  # The official documentation website is heavily rate-limited
+  #
+  # The documentation can be generated like this (replace 2019.2 with the correct tag):
+  # $ git clone https://github.com/saltstack/salt.git --branch 2019.2 --depth 1
+  # $ cd salt/doc
+  # $ pip install sphinx
+  # $ make html
+  #
+  # The generated html can be found in salt/doc/_build/html
+  class SaltStack < FileScraper
+    self.type = 'simple'
+    self.slug = 'saltstack'
+    self.release = '2019.2.0'
+    self.base_url = 'https://docs.saltstack.com/en/latest/'
+    self.root_path = 'ref/index.html'
+    self.links = {
+      home: 'https://www.saltstack.com/',
+      code: 'https://github.com/saltstack/salt'
+    }
+
+    html_filters.push 'salt_stack/clean_html', 'salt_stack/entries'
+
+    options[:only_patterns] = [/all\//]
+    options[:container] = '.body-content > .section'
+
+    options[:attribution] = <<-HTML
+      &copy; 2019 SaltStack.<br>
+      Licensed under the Apache License, Version 2.0.
+    HTML
+
+    def get_latest_version(opts)
+      get_latest_github_release('saltstack', 'salt', opts)
+    end
+  end
+end

+ 61 - 0
lib/docs/scrapers/scala.rb

@@ -0,0 +1,61 @@
+module Docs
+  class Scala < FileScraper
+    self.name = 'Scala'
+    self.type = 'scala'
+    self.links = {
+      home: 'http://www.scala-lang.org/',
+      code: 'https://github.com/scala/scala'
+    }
+
+    options[:container] = '#content-container'
+    options[:attribution] = <<-HTML
+        &copy; 2002-2019 EPFL, with contributions from Lightbend.<br>
+        Licensed under the Apache License, Version 2.0.
+    HTML
+
+    # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
+    # Extract api/scala-library into docs/scala~2.13_library
+    version '2.13 Library' do
+      self.release = '2.13.0'
+      self.base_url = 'https://www.scala-lang.org/api/2.13.0/'
+      self.root_path = 'index.html'
+
+      html_filters.push 'scala/entries', 'scala/clean_html'
+    end
+
+    # https://downloads.lightbend.com/scala/2.13.0/scala-docs-2.13.0.zip
+    # Extract api/scala-reflect into docs/scala~2.13_reflection
+    version '2.13 Reflection' do
+      self.release = '2.13.0'
+      self.base_url = 'https://www.scala-lang.org/api/2.13.0/scala-reflect/'
+      self.root_path = 'index.html'
+
+      html_filters.push 'scala/entries', 'scala/clean_html'
+    end
+
+    # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
+    # Extract api/scala-library into docs/scala~2.12_library
+    version '2.12 Library' do
+      self.release = '2.12.9'
+      self.base_url = 'https://www.scala-lang.org/api/2.12.9/'
+      self.root_path = 'index.html'
+
+      html_filters.push 'scala/entries', 'scala/clean_html'
+    end
+
+    # https://downloads.lightbend.com/scala/2.12.9/scala-docs-2.12.9.zip
+    # Extract api/scala-reflect into docs/scala~2.12_reflection
+    version '2.12 Reflection' do
+      self.release = '2.12.9'
+      self.base_url = 'https://www.scala-lang.org/api/2.12.9/scala-reflect/'
+      self.root_path = 'index.html'
+
+      html_filters.push 'scala/entries', 'scala/clean_html'
+    end
+
+    def get_latest_version(opts)
+      doc = fetch_doc('https://www.scala-lang.org/api/current/', opts)
+      doc.at_css('#doc-version').content
+    end
+  end
+end

+ 29 - 0
lib/docs/scrapers/vue_router.rb

@@ -0,0 +1,29 @@
+module Docs
+  class VueRouter < UrlScraper
+    self.name = 'Vue Router'
+    self.slug = 'vue_router'
+    self.type = 'simple'
+    self.release = '3.1.2'
+    self.base_url = 'https://router.vuejs.org/'
+    self.links = {
+      home: 'https://router.vuejs.org',
+      code: 'https://github.com/vuejs/vue-router'
+    }
+
+    html_filters.push 'vue_router/entries', 'vue_router/clean_html'
+
+    options[:skip_patterns] = [
+      # Other languages
+      /^(zh|ja|ru|kr|fr)\//,
+    ]
+
+    options[:attribution] = <<-HTML
+      &copy; 2013&ndash;present Evan You<br>
+      Licensed under the MIT License.
+    HTML
+
+    def get_latest_version(opts)
+      get_latest_github_release('vuejs', 'vue-router', opts)
+    end
+  end
+end

+ 27 - 0
lib/docs/scrapers/vuex.rb

@@ -0,0 +1,27 @@
+module Docs
+  class Vuex < UrlScraper
+    self.type = 'simple'
+    self.release = '3.1.1'
+    self.base_url = 'https://vuex.vuejs.org/'
+    self.links = {
+      home: 'https://vuex.vuejs.org',
+      code: 'https://github.com/vuejs/vuex'
+    }
+
+    html_filters.push 'vuex/entries', 'vuex/clean_html'
+
+    options[:skip_patterns] = [
+      # Other languages
+      /^(zh|ja|ru|kr|fr|ptbr)\//,
+    ]
+
+    options[:attribution] = <<-HTML
+      &copy; 2015&ndash;present Evan You<br>
+      Licensed under the MIT License.
+    HTML
+
+    def get_latest_version(opts)
+      get_npm_version('vuex', opts)
+    end
+  end
+end

+ 44 - 0
lib/docs/scrapers/wordpress.rb

@@ -0,0 +1,44 @@
+module Docs
+  class Wordpress < UrlScraper
+    self.name = 'WordPress'
+    self.type = 'wordpress'
+    self.release = '5.2.2'
+    self.base_url = 'https://developer.wordpress.org/reference/'
+    self.initial_paths = %w(
+      functions/
+      hooks/
+      classes/
+    )
+
+    self.links = {
+      home: 'https://wordpress.org/',
+      code: 'https://github.com/WordPress/WordPress'
+    }
+
+    html_filters.push 'wordpress/entries', 'wordpress/clean_html'
+
+    options[:container] = '#content-area'
+    options[:trailing_slash] = false
+    options[:only_patterns] = [
+      /\Afunctions\//,
+      /\Ahooks\//,
+      /\Aclasses\//
+    ]
+
+    options[:skip_patterns] = [
+      /\Afunctions\/page\/\d+/,
+      /\Ahooks\/page\/\d+/,
+      /\Aclasses\/page\/\d+/
+    ]
+
+    options[:attribution] = <<-HTML
+      &copy; 2003&ndash;2019 WordPress Foundation<br>
+      Licensed under the GNU GPLv2+ License.
+    HTML
+
+    def get_latest_version(opts)
+      doc = fetch_doc('https://wordpress.org/download/releases/', opts)
+      doc.at_css('.releases.latest td').content
+    end
+  end
+end

BIN
public/icons/docs/composer/16.png


BIN
public/icons/docs/composer/16@2x.png


+ 1 - 0
public/icons/docs/composer/SOURCE

@@ -0,0 +1 @@
+https://github.com/composer/getcomposer.org/blob/master/web/img/logo-composer-transparent.png

BIN
public/icons/docs/cypress/16.png


BIN
public/icons/docs/cypress/16@2x.png


+ 1 - 0
public/icons/docs/cypress/SOURCE

@@ -0,0 +1 @@
+https://github.com/cypress-io/cypress-documentation/raw/develop/themes/cypress/source/img/favicon.ico

BIN
public/icons/docs/godot/16.png


BIN
public/icons/docs/godot/16@2x.png


BIN
public/icons/docs/pony/16.png


BIN
public/icons/docs/pony/16@2x.png


+ 1 - 0
public/icons/docs/pony/SOURCE

@@ -0,0 +1 @@
+https://raw.githubusercontent.com/ponylang/ponylang-website/master/static/images/logo.png

BIN
public/icons/docs/rxjs/16.png


BIN
public/icons/docs/rxjs/16@2x.png


+ 1 - 0
public/icons/docs/rxjs/SOURCE

@@ -0,0 +1 @@
+https://github.com/ReactiveX/reactivex.github.io/blob/develop/favicon.ico

BIN
public/icons/docs/saltstack/16.png


BIN
public/icons/docs/saltstack/16@2x.png


+ 1 - 0
public/icons/docs/saltstack/SOURCE

@@ -0,0 +1 @@
+https://github.com/saltstack/salt/blob/develop/doc/_static/salt-logo.svg

BIN
public/icons/docs/scala/16.png


BIN
public/icons/docs/scala/16@2x.png


BIN
public/icons/docs/vue_router/16.png


BIN
public/icons/docs/vue_router/16@2x.png


+ 1 - 0
public/icons/docs/vue_router/SOURCE

@@ -0,0 +1 @@
+https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai

BIN
public/icons/docs/vuex/16.png


BIN
public/icons/docs/vuex/16@2x.png


+ 1 - 0
public/icons/docs/vuex/SOURCE

@@ -0,0 +1 @@
+https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai

BIN
public/icons/docs/wordpress/16.png


BIN
public/icons/docs/wordpress/16@2x.png


+ 1 - 0
public/icons/docs/wordpress/SOURCE

@@ -0,0 +1 @@
+https://wordpress.org/about/logos/

+ 37 - 5
views/service-worker.js.erb

@@ -11,6 +11,34 @@ const urlsToCache = [
   '<%= doc_index_urls.join "',\n  '" %>',
 ];
 
+<%# Clone a request with the mode set to 'cors' %>
+function corsify(request) {
+  const options = {
+    mode: 'cors',
+  };
+
+  const keys = [
+    'body',
+    'cache',
+    'credentials',
+    'headers',
+    'integrity',
+    'keepalive',
+    'method',
+    'redirect',
+    'referrer',
+    'referrerPolicy',
+    'signal',
+    'window',
+  ];
+
+  for (const key of keys) {
+    options[key] = request[key];
+  }
+
+  return new Request(request.url, options);
+}
+
 <%# Set-up the cache %>
 self.addEventListener('install', event => {
   self.skipWaiting();
@@ -36,9 +64,11 @@ self.addEventListener('fetch', event => {
     if (cachedResponse) return cachedResponse;
 
     try {
-      const response = await fetch(event.request);
+      const response = await fetch(corsify(event.request));
 
-      if (!response.ok) {
+      <%# If the status code is 0 it means the response is opaque %>
+      <%# If the response is opaque it's not possible to read whether it is successful or not, so we assume it is %>
+      if (!response.ok && response.status !== 0) {
         throw new Error(`The HTTP request failed with status code ${response.status}`);
       }
 
@@ -46,11 +76,13 @@ self.addEventListener('fetch', event => {
     } catch (err) {
       const url = new URL(event.request.url);
 
-      <%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>
-      <%# The index page will make sure the correct documentation or a proper offline page is shown  %>
       const pathname = url.pathname;
       const filename = pathname.substr(1 + pathname.lastIndexOf('/')).split(/\#|\?/g)[0];
-      if (url.origin === location.origin && !filename.includes('.')) {
+      const extensions = ['.html', '.css', '.js', '.json', '.png', '.ico', '.svg', '.xml'];
+
+      <%# Attempt to return the index page from the cache if the user is visiting a url like devdocs.io/offline or devdocs.io/javascript/global_objects/array/find %>
+      <%# The index page will make sure the correct documentation or a proper offline page is shown  %>
+      if (url.origin === location.origin && !extensions.some(ext => filename.endsWith(ext))) {
         const cachedIndex = await caches.match('/');
         if (cachedIndex) return cachedIndex;
       }