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! ' + 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; }