diff --git a/.github/CONTRIBUTING.md b/.github/CONTRIBUTING.md
index 99fd8584..0c53b8e9 100644
--- a/.github/CONTRIBUTING.md
+++ b/.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
diff --git a/assets/javascripts/lib/page.coffee b/assets/javascripts/lib/page.coffee
index ba2f2647..91d73432 100644
--- a/assets/javascripts/lib/page.coffee
+++ b/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
diff --git a/assets/javascripts/templates/pages/about_tmpl.coffee b/assets/javascripts/templates/pages/about_tmpl.coffee
index 0981b9a3..892c4284 100644
--- a/assets/javascripts/templates/pages/about_tmpl.coffee
+++ b/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
© 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.
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',
diff --git a/assets/javascripts/views/layout/document.coffee b/assets/javascripts/views/layout/document.coffee
index 597dfe37..a10d0b3c 100644
--- a/assets/javascripts/views/layout/document.coffee
+++ b/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
diff --git a/assets/stylesheets/application.css.scss b/assets/stylesheets/application.css.scss
index 7a09dd97..05f8bb2f 100644
--- a/assets/stylesheets/application.css.scss
+++ b/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';
diff --git a/assets/stylesheets/pages/_cypress.scss b/assets/stylesheets/pages/_cypress.scss
new file mode 100644
index 00000000..aa1108d2
--- /dev/null
+++ b/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
+ }
+ }
+}
diff --git a/assets/stylesheets/pages/_rxjs.scss b/assets/stylesheets/pages/_rxjs.scss
new file mode 100644
index 00000000..15e1252b
--- /dev/null
+++ b/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; }
+}
diff --git a/assets/stylesheets/pages/_scala.scss b/assets/stylesheets/pages/_scala.scss
new file mode 100644
index 00000000..b2beb118
--- /dev/null
+++ b/assets/stylesheets/pages/_scala.scss
@@ -0,0 +1,4 @@
+._scala {
+ @extend %simple;
+ .deprecated { @extend %label-red; }
+}
diff --git a/assets/stylesheets/pages/_wordpress.scss b/assets/stylesheets/pages/_wordpress.scss
new file mode 100644
index 00000000..1da15abd
--- /dev/null
+++ b/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;
+ }
+}
\ No newline at end of file
diff --git a/lib/docs/filters/composer/clean_html.rb b/lib/docs/filters/composer/clean_html.rb
new file mode 100644
index 00000000..54ce7a26
--- /dev/null
+++ b/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('
Composer
')
+ 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
diff --git a/lib/docs/filters/composer/entries.rb b/lib/docs/filters/composer/entries.rb
new file mode 100644
index 00000000..d3116756
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/cypress/clean_html.rb b/lib/docs/filters/cypress/clean_html.rb
new file mode 100644
index 00000000..9b93b313
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/cypress/entries.rb b/lib/docs/filters/cypress/entries.rb
new file mode 100644
index 00000000..664f4da7
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/pony/clean_html.rb b/lib/docs/filters/pony/clean_html.rb
new file mode 100644
index 00000000..9260f226
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/pony/entries.rb b/lib/docs/filters/pony/entries.rb
new file mode 100644
index 00000000..296f2090
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/rxjs/clean_html.rb b/lib/docs/filters/rxjs/clean_html.rb
new file mode 100644
index 00000000..864c201b
--- /dev/null
+++ b/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("#{title}
")
+ 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('')
+ 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(%(#{node['title']}
)) 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 = "#{node.inner_html}
"
+ 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
diff --git a/lib/docs/filters/rxjs/entries.rb b/lib/docs/filters/rxjs/entries.rb
new file mode 100644
index 00000000..c6e488fb
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/salt_stack/clean_html.rb b/lib/docs/filters/salt_stack/clean_html.rb
new file mode 100644
index 00000000..37e2a3b6
--- /dev/null
+++ b/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 = 'SaltStack
'
+ 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
diff --git a/lib/docs/filters/salt_stack/entries.rb b/lib/docs/filters/salt_stack/entries.rb
new file mode 100644
index 00000000..d346fa29
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/scala/clean_html.rb b/lib/docs/filters/scala/clean_html.rb
new file mode 100644
index 00000000..0320932d
--- /dev/null
+++ b/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 "#{signature.inner_html}
"
+
+ 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 ? "#{other.to_html}
" : ''
+
+ title_content = root_page? ? 'Package root' : "#{type} #{package}#{name}".strip
+ title = "#{title_content}
"
+ definition.replace title + other_content
+ end
+ end
+ end
+end
diff --git a/lib/docs/filters/scala/entries.rb b/lib/docs/filters/scala/entries.rb
new file mode 100644
index 00000000..5eff47fb
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/vue_router/clean_html.rb b/lib/docs/filters/vue_router/clean_html.rb
new file mode 100644
index 00000000..71c1a132
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/vue_router/entries.rb b/lib/docs/filters/vue_router/entries.rb
new file mode 100644
index 00000000..b38fed28
--- /dev/null
+++ b/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', 'API Reference'],
+ ['', '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 ' Props'
+ entry_name = " `#{entry_name}` prop"
+ when ' Props'
+ entry_name = " `#{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
diff --git a/lib/docs/filters/vuex/clean_html.rb b/lib/docs/filters/vuex/clean_html.rb
new file mode 100644
index 00000000..4aad55a9
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/vuex/entries.rb b/lib/docs/filters/vuex/entries.rb
new file mode 100644
index 00000000..04846fd2
--- /dev/null
+++ b/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
diff --git a/lib/docs/filters/wordpress/clean_html.rb b/lib/docs/filters/wordpress/clean_html.rb
new file mode 100644
index 00000000..32cf3b3f
--- /dev/null
+++ b/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 = 'WordPress
'
+ 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 = /
/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
diff --git a/lib/docs/filters/wordpress/entries.rb b/lib/docs/filters/wordpress/entries.rb
new file mode 100644
index 00000000..ba539d67
--- /dev/null
+++ b/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
diff --git a/lib/docs/scrapers/composer.rb b/lib/docs/scrapers/composer.rb
new file mode 100644
index 00000000..ade5ca83
--- /dev/null
+++ b/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
+ © Nils Adermann, Jordi Boggiano
+ Licensed under the MIT License.
+ HTML
+
+ def get_latest_version(opts)
+ get_latest_github_release('composer', 'composer', opts)
+ end
+ end
+end
diff --git a/lib/docs/scrapers/cypress.rb b/lib/docs/scrapers/cypress.rb
new file mode 100644
index 00000000..56380630
--- /dev/null
+++ b/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
+ © 2017 Cypress.io
+ Licensed under the MIT License.
+ HTML
+
+ def get_latest_version(opts)
+ get_latest_github_release('cypress-io', 'cypress', opts)
+ end
+ end
+end
diff --git a/lib/docs/scrapers/pony.rb b/lib/docs/scrapers/pony.rb
new file mode 100644
index 00000000..24df881d
--- /dev/null
+++ b/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
+ © 2016-2018, The Pony Developers
+ © 2014-2015, Causality Ltd.
+ 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
diff --git a/lib/docs/scrapers/rxjs.rb b/lib/docs/scrapers/rxjs.rb
new file mode 100644
index 00000000..e6f57961
--- /dev/null
+++ b/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
+ © 2015–2018 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors.
+ 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! '', 'live example'
+ response.body.gsub! ' .section'
+
+ options[:attribution] = <<-HTML
+ © 2019 SaltStack.
+ Licensed under the Apache License, Version 2.0.
+ HTML
+
+ def get_latest_version(opts)
+ get_latest_github_release('saltstack', 'salt', opts)
+ end
+ end
+end
diff --git a/lib/docs/scrapers/scala.rb b/lib/docs/scrapers/scala.rb
new file mode 100644
index 00000000..dc268960
--- /dev/null
+++ b/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
+ © 2002-2019 EPFL, with contributions from Lightbend.
+ 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
diff --git a/lib/docs/scrapers/vue_router.rb b/lib/docs/scrapers/vue_router.rb
new file mode 100644
index 00000000..9f617944
--- /dev/null
+++ b/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
+ © 2013–present Evan You
+ Licensed under the MIT License.
+ HTML
+
+ def get_latest_version(opts)
+ get_latest_github_release('vuejs', 'vue-router', opts)
+ end
+ end
+end
diff --git a/lib/docs/scrapers/vuex.rb b/lib/docs/scrapers/vuex.rb
new file mode 100644
index 00000000..57e761ce
--- /dev/null
+++ b/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
+ © 2015–present Evan You
+ Licensed under the MIT License.
+ HTML
+
+ def get_latest_version(opts)
+ get_npm_version('vuex', opts)
+ end
+ end
+end
diff --git a/lib/docs/scrapers/wordpress.rb b/lib/docs/scrapers/wordpress.rb
new file mode 100644
index 00000000..b1bf46c9
--- /dev/null
+++ b/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
+ © 2003–2019 WordPress Foundation
+ 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
diff --git a/public/icons/docs/composer/16.png b/public/icons/docs/composer/16.png
new file mode 100644
index 00000000..bfbdb4d9
Binary files /dev/null and b/public/icons/docs/composer/16.png differ
diff --git a/public/icons/docs/composer/16@2x.png b/public/icons/docs/composer/16@2x.png
new file mode 100644
index 00000000..0dea6b00
Binary files /dev/null and b/public/icons/docs/composer/16@2x.png differ
diff --git a/public/icons/docs/composer/SOURCE b/public/icons/docs/composer/SOURCE
new file mode 100644
index 00000000..ea9acaa7
--- /dev/null
+++ b/public/icons/docs/composer/SOURCE
@@ -0,0 +1 @@
+https://github.com/composer/getcomposer.org/blob/master/web/img/logo-composer-transparent.png
diff --git a/public/icons/docs/cypress/16.png b/public/icons/docs/cypress/16.png
new file mode 100644
index 00000000..4da1b400
Binary files /dev/null and b/public/icons/docs/cypress/16.png differ
diff --git a/public/icons/docs/cypress/16@2x.png b/public/icons/docs/cypress/16@2x.png
new file mode 100644
index 00000000..a327f375
Binary files /dev/null and b/public/icons/docs/cypress/16@2x.png differ
diff --git a/public/icons/docs/cypress/SOURCE b/public/icons/docs/cypress/SOURCE
new file mode 100644
index 00000000..3ea99830
--- /dev/null
+++ b/public/icons/docs/cypress/SOURCE
@@ -0,0 +1 @@
+https://github.com/cypress-io/cypress-documentation/raw/develop/themes/cypress/source/img/favicon.ico
diff --git a/public/icons/docs/godot/16.png b/public/icons/docs/godot/16.png
index c574a60d..5497dfaf 100644
Binary files a/public/icons/docs/godot/16.png and b/public/icons/docs/godot/16.png differ
diff --git a/public/icons/docs/godot/16@2x.png b/public/icons/docs/godot/16@2x.png
index 8047384d..64cdf210 100644
Binary files a/public/icons/docs/godot/16@2x.png and b/public/icons/docs/godot/16@2x.png differ
diff --git a/public/icons/docs/pony/16.png b/public/icons/docs/pony/16.png
new file mode 100644
index 00000000..c5b30dad
Binary files /dev/null and b/public/icons/docs/pony/16.png differ
diff --git a/public/icons/docs/pony/16@2x.png b/public/icons/docs/pony/16@2x.png
new file mode 100644
index 00000000..daed6509
Binary files /dev/null and b/public/icons/docs/pony/16@2x.png differ
diff --git a/public/icons/docs/pony/SOURCE b/public/icons/docs/pony/SOURCE
new file mode 100644
index 00000000..c39ba900
--- /dev/null
+++ b/public/icons/docs/pony/SOURCE
@@ -0,0 +1 @@
+https://raw.githubusercontent.com/ponylang/ponylang-website/master/static/images/logo.png
diff --git a/public/icons/docs/rxjs/16.png b/public/icons/docs/rxjs/16.png
new file mode 100644
index 00000000..790f8390
Binary files /dev/null and b/public/icons/docs/rxjs/16.png differ
diff --git a/public/icons/docs/rxjs/16@2x.png b/public/icons/docs/rxjs/16@2x.png
new file mode 100644
index 00000000..a45cd5cb
Binary files /dev/null and b/public/icons/docs/rxjs/16@2x.png differ
diff --git a/public/icons/docs/rxjs/SOURCE b/public/icons/docs/rxjs/SOURCE
new file mode 100644
index 00000000..2a3b3084
--- /dev/null
+++ b/public/icons/docs/rxjs/SOURCE
@@ -0,0 +1 @@
+https://github.com/ReactiveX/reactivex.github.io/blob/develop/favicon.ico
diff --git a/public/icons/docs/saltstack/16.png b/public/icons/docs/saltstack/16.png
new file mode 100644
index 00000000..ee2631f8
Binary files /dev/null and b/public/icons/docs/saltstack/16.png differ
diff --git a/public/icons/docs/saltstack/16@2x.png b/public/icons/docs/saltstack/16@2x.png
new file mode 100644
index 00000000..3fe90907
Binary files /dev/null and b/public/icons/docs/saltstack/16@2x.png differ
diff --git a/public/icons/docs/saltstack/SOURCE b/public/icons/docs/saltstack/SOURCE
new file mode 100644
index 00000000..e0cd3832
--- /dev/null
+++ b/public/icons/docs/saltstack/SOURCE
@@ -0,0 +1 @@
+https://github.com/saltstack/salt/blob/develop/doc/_static/salt-logo.svg
diff --git a/public/icons/docs/scala/16.png b/public/icons/docs/scala/16.png
new file mode 100644
index 00000000..429ed7df
Binary files /dev/null and b/public/icons/docs/scala/16.png differ
diff --git a/public/icons/docs/scala/16@2x.png b/public/icons/docs/scala/16@2x.png
new file mode 100644
index 00000000..bb92e2d3
Binary files /dev/null and b/public/icons/docs/scala/16@2x.png differ
diff --git a/public/icons/docs/vue_router/16.png b/public/icons/docs/vue_router/16.png
new file mode 100644
index 00000000..153c58cd
Binary files /dev/null and b/public/icons/docs/vue_router/16.png differ
diff --git a/public/icons/docs/vue_router/16@2x.png b/public/icons/docs/vue_router/16@2x.png
new file mode 100644
index 00000000..7a277fae
Binary files /dev/null and b/public/icons/docs/vue_router/16@2x.png differ
diff --git a/public/icons/docs/vue_router/SOURCE b/public/icons/docs/vue_router/SOURCE
new file mode 100644
index 00000000..6999e8c5
--- /dev/null
+++ b/public/icons/docs/vue_router/SOURCE
@@ -0,0 +1 @@
+https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai
diff --git a/public/icons/docs/vuex/16.png b/public/icons/docs/vuex/16.png
new file mode 100644
index 00000000..153c58cd
Binary files /dev/null and b/public/icons/docs/vuex/16.png differ
diff --git a/public/icons/docs/vuex/16@2x.png b/public/icons/docs/vuex/16@2x.png
new file mode 100644
index 00000000..7a277fae
Binary files /dev/null and b/public/icons/docs/vuex/16@2x.png differ
diff --git a/public/icons/docs/vuex/SOURCE b/public/icons/docs/vuex/SOURCE
new file mode 100644
index 00000000..6999e8c5
--- /dev/null
+++ b/public/icons/docs/vuex/SOURCE
@@ -0,0 +1 @@
+https://github.com/vuejs/vuejs.org/blob/master/assets/logo.ai
diff --git a/public/icons/docs/wordpress/16.png b/public/icons/docs/wordpress/16.png
new file mode 100644
index 00000000..0b3dc1cd
Binary files /dev/null and b/public/icons/docs/wordpress/16.png differ
diff --git a/public/icons/docs/wordpress/16@2x.png b/public/icons/docs/wordpress/16@2x.png
new file mode 100644
index 00000000..9de430fe
Binary files /dev/null and b/public/icons/docs/wordpress/16@2x.png differ
diff --git a/public/icons/docs/wordpress/SOURCE b/public/icons/docs/wordpress/SOURCE
new file mode 100644
index 00000000..1c817d62
--- /dev/null
+++ b/public/icons/docs/wordpress/SOURCE
@@ -0,0 +1 @@
+https://wordpress.org/about/logos/
\ No newline at end of file
diff --git a/views/service-worker.js.erb b/views/service-worker.js.erb
index aa234c19..4a0bd9d0 100644
--- a/views/service-worker.js.erb
+++ b/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;
}