Merge remote-tracking branch 'origin/main' into cpp

pull/1606/head
Simon Legner 3 years ago
commit 885783f36d

@ -10,9 +10,8 @@ Want to contribute? Great. Please review the following guidelines carefully and
4. [Contributing code and features](#contributing-code-and-features)
5. [Contributing new documentations](#contributing-new-documentations)
6. [Updating existing documentations](#updating-existing-documentations)
7. [Other contributions](#other-contributions)
8. [Coding conventions](#coding-conventions)
9. [Questions?](#questions)
7. [Coding conventions](#coding-conventions)
8. [Questions?](#questions)
## Reporting bugs

@ -1,6 +1,6 @@
---
name: Bug report
about: Create a report to help us improve Devdocs
about: Create a report to help us improve DevDocs
title: ''
labels: 'bug'
assignees: ''
@ -34,7 +34,7 @@ encountered it
## More resources
<!--
Add images, GIFs, screenshot, console output or any other resource that might help to undestand this bug
Add images, GIFs, screenshot, console output or any other resource that might help to understand this bug
-->
## Possible fix

@ -24,7 +24,7 @@ Add a description about how the documentation should be
## Actual style
<!--
Add images or urls of the miss-formatted devdocs documentation
Add images or urls of the miss-formatted DevDocs documentation
-->
## Expected style

@ -13,7 +13,7 @@ If possible fill each section
# Feature request
<!--
Verify this steps before suggeting a new feature:
Verify this steps before suggesting a new feature:
- Check if anyone has suggested this feature before
https://github.com/freeCodeCamp/devdocs/labels/feature
@ -25,12 +25,12 @@ Verify this steps before suggeting a new feature:
## Summary
<!--
Write a description of this feature and write why it should be added to Devdocs
Write a description of this feature and write why it should be added to DevDocs
-->
## Examples
<!--
If you have seen this feature before you can add images, URLs, gifs and any other
resouce that might help to undestand how this feature works
If you have seen this feature before you can add images, URLs, GIFs and any other
resource that might help to understand how this feature works
-->

@ -16,13 +16,13 @@ If youre adding a new scraper, please ensure that you have:
- [ ] `16@2x.png`: a 32×32 pixel icon for the doc
- [ ] `SOURCE`: A text file containing the URL to the page the image can be found on or the URL of the original image itself
<!-- SECTION B - Updating an existing documentation to it's latest version -->
<!-- SECTION B - Updating an existing documentation to its latest version -->
<!-- See https://github.com/freeCodeCamp/devdocs/blob/main/.github/CONTRIBUTING.md#updating-existing-documentations -->
If you're updating an existing documentation to it's latest version, please ensure that you have:
If you're updating existing documentation to its latest version, please ensure that you have:
- [ ] Updated the versions and releases in the scraper file
- [ ] Ensured the license is up-to-date and that the documentation's entry in the array in `about_tmpl.coffee` matches it's data in `self.attribution`
- [ ] Ensured the license is up-to-date and that the documentation's entry in the array in `about_tmpl.coffee` matches its data in `self.attribution`
- [ ] Ensured the icons and the `SOURCE` file in <code>public/icons/*your_scraper_name*/</code> are up-to-date if the documentation has a custom icon
- [ ] Ensured `self.links` contains up-to-date urls if `self.links` is defined
- [ ] Tested the changes locally to ensure:

@ -11,9 +11,9 @@ jobs:
runs-on: ubuntu-20.04
if: github.repository == 'freeCodeCamp/devdocs'
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v2.4.2
- name: Set up Ruby
uses: ruby/setup-ruby@v1.81.0
uses: ruby/setup-ruby@v1.110.0
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests

@ -8,9 +8,9 @@ jobs:
runs-on: ubuntu-20.04
if: github.repository == 'freeCodeCamp/devdocs'
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v2.4.2
- name: Set up Ruby
uses: ruby/setup-ruby@v1.81.0
uses: ruby/setup-ruby@v1.110.0
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Generate report

@ -9,9 +9,9 @@ jobs:
test:
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2.3.4
- uses: actions/checkout@v2.4.2
- name: Set up Ruby
uses: ruby/setup-ruby@v1.81.0
uses: ruby/setup-ruby@v1.110.0
with:
bundler-cache: true # runs 'bundle install' and caches installed gems automatically
- name: Run tests

4
.gitignore vendored

@ -8,3 +8,7 @@ public/docs/**/*
docs/**/*
!docs/*.md
/vendor
*.tar
*.tar.bz2
*.tar.gz
*.zip

@ -1 +1 @@
2.7.4
2.7.6

@ -1,4 +1,4 @@
FROM ruby:2.7.4
FROM ruby:2.7.6
ENV LANG=C.UTF-8
ENV ENABLE_SERVICE_WORKER=true

@ -7,7 +7,7 @@ GEM
minitest (~> 5.1)
tzinfo (~> 1.1)
backports (3.15.0)
better_errors (2.5.1)
better_errors (2.9.1)
coderay (>= 1.0.0)
erubi (>= 1.0.0)
rack (>= 0.9.0)
@ -20,13 +20,13 @@ GEM
coffee-script-source (1.12.2)
concurrent-ruby (1.1.9)
daemons (1.4.0)
erubi (1.9.0)
erubi (1.10.0)
ethon (0.12.0)
ffi (>= 1.3.0)
eventmachine (1.2.7)
execjs (2.7.0)
exifr (1.3.6)
ffi (1.12.2)
ffi (1.15.5)
fspath (3.1.2)
highline (2.0.3)
html-pipeline (2.12.0)
@ -46,16 +46,18 @@ GEM
image_size (2.0.2)
in_threads (1.5.3)
method_source (1.0.0)
mini_portile2 (2.4.0)
mini_portile2 (2.8.0)
minitest (5.14.4)
multi_json (1.13.1)
mustermann (1.0.3)
mustermann (1.1.1)
ruby2_keywords (~> 0.0.1)
net-sftp (3.0.0.beta1)
net-ssh (>= 5.0.0, < 6.0.0)
net-ssh (5.2.0)
newrelic_rpm (6.7.0.359)
nokogiri (1.10.4)
mini_portile2 (~> 2.4.0)
nokogiri (1.13.6)
mini_portile2 (~> 2.8.0)
racc (~> 1.4)
options (2.3.2)
progress (3.5.2)
progress_bar (1.3.0)
@ -64,7 +66,8 @@ GEM
pry (0.14.1)
coderay (~> 1.1)
method_source (~> 1.0)
rack (2.2.3)
racc (1.6.0)
rack (2.2.3.1)
rack-protection (2.0.7)
rack
rack-ssl-enforcer (0.2.9)
@ -75,6 +78,7 @@ GEM
rb-inotify (0.10.0)
ffi (~> 1.0)
rr (1.2.1)
ruby2_keywords (0.0.5)
sass (3.7.4)
sass-listen (~> 4.0.0)
sass-listen (4.0.0)
@ -128,7 +132,7 @@ GEM
unicode-display_width (1.6.0)
unicode_utils (1.4.0)
unix_utils (0.0.15)
yajl-ruby (1.4.1)
yajl-ruby (1.4.3)
PLATFORMS
ruby

@ -22,7 +22,7 @@ Unless you wish to contribute to the project, we recommend using the hosted vers
DevDocs is made of two pieces: a Ruby scraper that generates the documentation and metadata, and a JavaScript app powered by a small Sinatra app.
DevDocs requires Ruby 2.6.x, libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). Once you have these installed, run the following commands:
DevDocs requires Ruby 2.7.4, libcurl, and a JavaScript runtime supported by [ExecJS](https://github.com/rails/execjs#readme) (included in OS X and Windows; [Node.js](https://nodejs.org/en/) on Linux). Once you have these installed, run the following commands:
```sh
git clone https://github.com/freeCodeCamp/devdocs.git && cd devdocs
@ -147,8 +147,6 @@ Made something cool? Feel free to open a PR to add a new row to this table!
| Project | Description | Last commit |
|---------------------------------------------------------------------------------------------------------|------------------------------------------------------------------------|----------------------------------------------------------------------------------------------------------------------------------------------------------------------------|
| [Chrome web app](https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe) | Chrome Web App which adds a shortcut to DevDocs apps page. | N/A |
| [Ubuntu Touch app](https://uappexplorer.com/app/devdocsunofficial.berkes) | Application for devices running Ubuntu Touch. | N/A |
| [Sublime Text plugin](https://sublime.wbond.net/packages/DevDocs) | Sublime Text plugin to search DevDocs by selection or by input. | [![Latest GitHub commit](https://img.shields.io/github/last-commit/vitorbritto/sublime-devdocs?logo=github&label)](https://github.com/vitorbritto/sublime-devdocs) |
| [Atom plugin](https://atom.io/packages/devdocs) | Atom plugin adding the `doc` command to search DevDocs. | [![Latest GitHub commit](https://img.shields.io/github/last-commit/masnun/atom-devdocs?logo=github&label)](https://github.com/masnun/atom-devdocs) |
| [gruehle/dev-docs-viewer](https://github.com/gruehle/dev-docs-viewer) | Brackets extension for searching and viewing DevDocs content. | [![Latest GitHub commit](https://img.shields.io/github/last-commit/gruehle/dev-docs-viewer?logo=github&label)](https://github.com/gruehle/dev-docs-viewer) |

@ -24,7 +24,12 @@ class app.Settings
'news'
]
LAYOUTS: ['_max-width', '_sidebar-hidden', '_native-scrollbars']
LAYOUTS: [
'_max-width'
'_sidebar-hidden'
'_native-scrollbars'
'_text-justify-hyphenate'
]
@defaults:
count: 0
@ -153,7 +158,8 @@ class app.Settings
toggleLayout: (layout, enable) ->
classList = document.body.classList
classList.toggle(layout, enable) unless app.router?.isSettings
# sidebar is always shown for settings; its state is updated in app.views.Settings
classList.toggle(layout, enable) unless layout is '_sidebar-hidden' and app.router?.isSettings
classList.toggle('_overlay-scrollbars', $.overlayScrollbarsEnabled())
return

@ -200,6 +200,8 @@ page.track = (fn) ->
track = ->
return unless app.config.env == 'production'
return if navigator.doNotTrack == '1'
return if navigator.globalPrivacyControl
consentGiven = Cookies.get('analyticsConsent')
consentAsked = Cookies.get('analyticsConsentAsked')

@ -1,4 +1,57 @@
[
[
"2022-05-03",
"New documentations: <a href=\"/kubernetes/\">Kubernetes</a>, <a href=\"/kubectl/\">Kubectl</a>"
],
[
"2022-04-25",
"New documentation: <a href=\"/nix/\">Nix</a>"
],
[
"2022-03-31",
"New documentation: <a href=\"/eigen3/\">Eigen3</a>"
],
[
"2022-02-21",
"New documentation: <a href=\"/tailwindcss/\">Tailwind CSS</a>"
],
[
"2022-01-12",
"New documentation: <a href=\"/react_router/\">React Router</a>"
],
[
"2022-01-09",
"New documentation: <a href=\"/deno/\">Deno</a>"
],
[
"2021-12-29",
"New documentation: <a href=\"/point_cloud_library/\">PointCloudLibrary</a>"
],
[
"2021-12-27",
"New documentation: <a href=\"/zig/\">Zig</a>"
],
[
"2021-12-26",
"New documentation: <a href=\"/gnu_make/\">GNU Make</a>"
],
[
"2021-12-07",
"New documentation: <a href=\"/prettier/\">Prettier</a>",
"Renamed documentation: <a href=\"/dom/\">Web APIs</a>"
],
[
"2021-12-05",
"New documentation: <a href=\"/esbuild/\">esbuild</a>"
],
[
"2021-12-04",
"New documentation: <a href=\"/vite/\">Vite</a>"
],
[
"2021-11-29",
"New documentation: <a href=\"/i3/\">i3</a>"
],
[
"2021-06-09",
"New documentation: <a href=\"/r/\">R</a>"

@ -30,7 +30,6 @@ app.templates.aboutPage = -> """
<h2 class="_block-heading" id="plugins">Plugins and Extensions</h2>
<ul>
<li><a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome web app</a>
<li><a href="https://github.com/egoist/devdocs-app">Desktop app</a>
<li><a href="https://sublime.wbond.net/packages/DevDocs">Sublime Text package</a>
<li><a href="https://atom.io/packages/devdocs">Atom package</a>
@ -82,15 +81,16 @@ app.templates.aboutPage = -> """
"""
credits = [
[ 'Angular.js',
'2010-2020 Google, Inc.',
'CC BY 3.0',
'https://creativecommons.org/licenses/by/3.0/'
], [
[
'Angular',
'2010-2021 Google, Inc.',
'2010-2022 Google, Inc.',
'CC BY',
'https://creativecommons.org/licenses/by/4.0/'
], [
'Angular.js',
'2010-2020 Google, Inc.',
'CC BY 3.0',
'https://creativecommons.org/licenses/by/3.0/'
], [
'Ansible',
'2012-2018 Michael DeHaan<br>&copy; 20182021 Red Hat, Inc.',
@ -183,12 +183,12 @@ credits = [
'https://raw.githubusercontent.com/Codeception/CodeceptJS/master/LICENSE'
], [
'CodeIgniter',
'2014-2020 British Columbia Institute of Technology',
'2014-2021 British Columbia Institute of Technology',
'MIT',
'https://raw.githubusercontent.com/bcit-ci/CodeIgniter/develop/license.txt'
], [
'CoffeeScript',
'2009-2020 Jeremy Ashkenas',
'2009-2022 Jeremy Ashkenas',
'MIT',
'https://raw.githubusercontent.com/jashkenas/coffeescript/master/LICENSE'
], [
@ -201,16 +201,16 @@ credits = [
'2012, 2013, 2015 The Apache Software Foundation',
'Apache',
'https://raw.githubusercontent.com/apache/cordova-docs/master/LICENSE'
], [
'CSS<br>DOM<br>HTTP<br>HTML<br>JavaScript<br>SVG<br>XPath',
'2005-2021 MDN contributors',
'CC BY-SA',
'https://creativecommons.org/licenses/by-sa/2.5/'
], [
'Crystal',
'2012-2021 Manas Technology Solutions',
'2012-2022 Manas Technology Solutions',
'Apache',
'https://raw.githubusercontent.com/crystal-lang/crystal/master/LICENSE'
], [
'CSS<br>DOM<br>HTTP<br>HTML<br>JavaScript<br>SVG<br>XPath',
'2005-2022 MDN contributors',
'CC BY-SA',
'https://creativecommons.org/licenses/by-sa/2.5/'
], [
'Cypress',
'2017 Cypress.io',
@ -223,7 +223,7 @@ credits = [
'https://raw.githubusercontent.com/dlang/phobos/master/LICENSE_1_0.txt'
], [
'D3.js',
'2010-2020 Michael Bostock',
'2010-2022 Michael Bostock',
'BSD',
'https://raw.githubusercontent.com/d3/d3/master/LICENSE'
], [
@ -231,6 +231,11 @@ credits = [
'2012 the Dart project authors',
'CC BY-SA',
'https://creativecommons.org/licenses/by-sa/4.0/'
], [
'Deno',
'20182022 the Deno authors',
'MIT',
'https://raw.githubusercontent.com/denoland/manual/main/LICENSE'
], [
'Django',
'Django Software Foundation and individual contributors',
@ -256,21 +261,26 @@ credits = [
'2001-2015 by the original authors<br>Drupal is a registered trademark of Dries Buytaert.',
'GPLv2',
'https://api.drupal.org/api/drupal/LICENSE.txt'
],[
'Eigen3',
'Eigen',
'MPL2',
'https://www.mozilla.org/en-US/MPL/2.0/'
], [
'Electron',
'GitHub Inc.',
'MIT',
'https://raw.githubusercontent.com/electron/electron/master/LICENSE'
], [
'Elixir',
'2012 Plataformatec',
'Apache',
'https://raw.githubusercontent.com/elixir-lang/elixir/master/LICENSE'
], [
'Elisp',
'1990-1996, 1998-2021 Free Software Foundation, Inc.',
'GPLv3',
'https://www.gnu.org/licenses/gpl-3.0.html'
], [
'Elixir',
'2012 Plataformatec',
'Apache',
'https://raw.githubusercontent.com/elixir-lang/elixir/master/LICENSE'
], [
'Ember.js',
'2020 Yehuda Katz, Tom Dale and Ember.js contributors',
@ -286,9 +296,14 @@ credits = [
'2010-2021 Ericsson AB',
'Apache',
'https://raw.githubusercontent.com/erlang/otp/maint/LICENSE.txt'
], [
'esbulid',
'2020 Evan Wallace',
'MIT',
'https://raw.githubusercontent.com/evanw/esbuild/blob/master/LICENSE.md'
], [
'ESLint',
'JS Foundation and other contributors',
'OpenJS Foundation and other contributors',
'MIT',
'https://raw.githubusercontent.com/eslint/eslint/master/LICENSE'
], [
@ -303,22 +318,22 @@ credits = [
'https://raw.githubusercontent.com/falconry/falcon/master/LICENSE'
], [
'Fish',
'20052009 Axel Liljencrantz, 20092021 fish-shell contributors',
'20052009 Axel Liljencrantz, 20092022 fish-shell contributors',
'GPLv2',
'https://fishshell.com/docs/current/license.html'
], [
'Flask',
'2007-2021 Pallets',
'2007-2022 Pallets',
'BSD',
'https://github.com/pallets/flask/blob/master/LICENSE.rst'
], [
'GCC<br>GNU Fortran',
'GCC<br>GNU Fortran<br>GNU Make',
'Free Software Foundation',
'GFDL',
'https://www.gnu.org/licenses/fdl-1.3.en.html'
], [
'Git',
'2012-2021 Scott Chacon and others',
'2012-2022 Scott Chacon and others',
'MIT',
'https://raw.githubusercontent.com/git/git-scm.com/master/MIT-LICENSE.txt'
], [
@ -348,7 +363,7 @@ credits = [
'https://raw.githubusercontent.com/graphite-project/graphite-web/master/LICENSE'
], [
'Groovy',
'2003-2020 The Apache Software Foundation',
'2003-2022 The Apache Software Foundation',
'Apache',
'https://github.com/apache/groovy-website/blob/asf-site/LICENSE'
], [
@ -386,11 +401,16 @@ credits = [
'2009-present Homebrew contributors',
'BSD',
'https://raw.githubusercontent.com/Homebrew/brew/master/LICENSE.txt'
], [
'i3',
'2009, Michael Stapelberg and contributors',
'BSD',
'https://raw.githubusercontent.com/i3/i3/next/LICENSE'
], [
'Immutable.js',
'2014-2016 Facebook, Inc.',
'2014present Lee Byron and other contributors',
'BSD',
'https://raw.githubusercontent.com/facebook/immutable-js/master/LICENSE'
'https://github.com/immutable-js/immutable-js/blob/main/LICENSE'
], [
'InfluxData',
'2015 InfluxData, Inc.',
@ -408,7 +428,7 @@ credits = [
'https://raw.githubusercontent.com/jekyll/jekyll/master/LICENSE'
], [
'Jest',
'2021 Facebook, Inc.',
'2022 Facebook, Inc.',
'MIT',
'https://raw.githubusercontent.com/facebook/jest/master/LICENSE'
], [
@ -436,16 +456,16 @@ credits = [
'jQuery Foundation and other contributors',
'MIT',
'https://raw.githubusercontent.com/jquery/api.jqueryui.com/master/LICENSE.txt'
], [
'Julia',
'2009-2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',
'MIT',
'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'
], [
'JSDoc',
'2011-2017 the contributors to the JSDoc 3 documentation project',
'CC BY-SA',
'https://raw.githubusercontent.com/jsdoc3/jsdoc3.github.com/master/LICENSE'
], [
'Julia',
'2009-2021 Jeff Bezanson, Stefan Karpinski, Viral B. Shah, and other contributors',
'MIT',
'https://raw.githubusercontent.com/JuliaLang/julia/master/LICENSE.md'
], [
'Knockout.js',
'Steven Sanderson, the Knockout.js team, and other contributors',
@ -458,9 +478,14 @@ credits = [
'https://raw.githubusercontent.com/koajs/koa/master/LICENSE'
], [
'Kotlin',
'2010-2020 JetBrains s.r.o. and Kotlin Programming Language contributors',
'2010-2022 JetBrains s.r.o. and Kotlin Programming Language contributors',
'Apache',
'https://raw.githubusercontent.com/JetBrains/kotlin/master/license/LICENSE.txt'
], [
'Kubernetes',
'2022 The Kubernetes Authors and The Linux Foundation',
'Apache License 2.0',
'https://github.com/kubernetes/kubernetes/blob/master/LICENSE'
], [
'Laravel',
'Taylor Otwell',
@ -473,7 +498,7 @@ credits = [
'https://ctan.org/pkg/latex2e-help-texinfo/'
], [
'Leaflet',
'2010-2019 Vladimir Agafonkin<br>&copy; 2010-2011, CloudMade<br>Maps &copy; OpenStreetMap contributors.',
'2010-2022 Vladimir Agafonkin<br>&copy; 2010-2011, CloudMade<br>Maps &copy; OpenStreetMap contributors.',
'BSD',
'https://raw.githubusercontent.com/Leaflet/Leaflet/master/LICENSE'
], [
@ -497,8 +522,8 @@ credits = [
'MIT',
'http://www.lua.org/license.html'
], [
'L&Ouml;VE',
'2006-2020 L&Ouml;VE Development Team',
'LÖVE',
'2006-2020 LÖVE Development Team',
'GFDL',
'http://www.gnu.org/copyleft/fdl.html'
], [
@ -553,7 +578,7 @@ credits = [
'https://github.com/LearnBoost/mongoose/blob/master/README.md#license'
], [
'nginx',
'2002-2021 Igor Sysoev<br>&copy; 2011-2021 Nginx, Inc.',
'2002-2021 Igor Sysoev<br>&copy; 2011-2022 Nginx, Inc.',
'BSD',
'http://nginx.org/LICENSE'
], [
@ -566,6 +591,11 @@ credits = [
'2006-2021 Andreas Rumpf',
'MIT',
'https://github.com/nim-lang/Nim#license'
], [
'Nix',
'2022 NixOS Contributors',
'LGPLv2.1',
'https://github.com/NixOS/nix#license'
], [
'Node.js',
'Joyent, Inc. and other Node contributors<br>Node.js is a trademark of Joyent, Inc.',
@ -573,7 +603,7 @@ credits = [
'https://raw.githubusercontent.com/nodejs/node/master/LICENSE'
], [
'Nokogiri',
'2008-2018 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo, Patrick Mahoney, Yoko Harada, Akinori MUSHA, John Shahid, Lars Kanis',
'20082021 Aaron Patterson, Mike Dalessio, Charles Nutter, Sergio Arbeo, Patrick Mahoney, Yoko Harada, Akinori MUSHA, John Shahid, Lars Kanis',
'MIT',
'https://raw.githubusercontent.com/sparklemotion/nokogiri/master/LICENSE.md'
], [
@ -588,17 +618,17 @@ credits = [
'https://raw.githubusercontent.com/numpy/numpy/master/LICENSE.txt'
], [
'OCaml',
'1995-2021 INRIA',
'1995-2022 INRIA',
'CC BY-SA',
'https://ocaml.org/docs/'
], [
'Octave',
'1996-2018 John W. Eaton',
'19962022 The Octave Project Developers',
'Octave',
'https://octave.org/doc/interpreter/'
'https://octave.org/doc/v7.1.0/'
], [
'OpenJDK',
'1993, 2020, Oracle and/or its affiliates. All rights reserved.<br>Licensed under the GNU General Public License, version 2, with the Classpath Exception.<br>Various third party code in OpenJDK is licensed under different licenses.<br>Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.',
'1993, 2022, Oracle and/or its affiliates. All rights reserved.<br>Licensed under the GNU General Public License, version 2, with the Classpath Exception.<br>Various third party code in OpenJDK is licensed under different licenses.<br>Java and OpenJDK are trademarks or registered trademarks of Oracle and/or its affiliates.',
'GPLv2',
'http://openjdk.java.net/legal/gplv2+ce.html'
], [
@ -613,12 +643,12 @@ credits = [
'https://raw.githubusercontent.com/padrino/padrino-framework/master/padrino/LICENSE.txt'
], [
'pandas',
'2008-2020, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team',
'2008-2022, AQR Capital Management, LLC, Lambda Foundry, Inc. and PyData Development Team',
'BSD',
'https://raw.githubusercontent.com/pydata/pandas/master/LICENSE'
], [
'Perl',
'1993-2020 Larry Wall and others',
'1993-2021 Larry Wall and others',
'GPLv1',
'https://perldoc.perl.org/index-licence.html'
], [
@ -638,7 +668,7 @@ credits = [
'https://raw.githubusercontent.com/phoenixframework/phoenix/master/LICENSE.md'
], [
'PHP',
'1997-2018 The PHP Documentation Group',
'1997-2021 The PHP Documentation Group',
'CC BY',
'https://secure.php.net/manual/en/copyright.php'
], [
@ -646,6 +676,11 @@ credits = [
'2005-2017 Sebastian Bergmann',
'CC BY',
'https://creativecommons.org/licenses/by/3.0/'
], [
'PointCloudLibrary',
'20092012, Willow Garage, Inc.<br>&copy; 2012, Open Perception, Inc.',
'BSD',
'https://raw.githubusercontent.com/PointCloudLibrary/pcl/master/LICENSE.txt'
], [
'Pony',
'2016-2020, The Pony Developers & 2014-2015, Causality Ltd.',
@ -653,9 +688,14 @@ credits = [
'https://raw.githubusercontent.com/ponylang/ponyc/master/LICENSE'
], [
'PostgreSQL',
'1996-2021 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
'1996-2022 The PostgreSQL Global Development Group<br>&copy; 1994 The Regents of the University of California',
'PostgreSQL',
'https://www.postgresql.org/about/licence/'
], [
'Prettier',
'James Long and contributors',
'MIT',
'https://raw.githubusercontent.com/prettier/prettier/main/LICENSE '
], [
'Puppeteer',
'2021 Google Inc',
@ -668,7 +708,7 @@ credits = [
'https://raw.githubusercontent.com/pygame/pygame/master/LICENSE'
], [
'Python',
'2001-2021 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',
'2001-2022 Python Software Foundation<br>Python is a trademark of the Python Software Foundation.',
'PSFL',
'https://docs.python.org/3/license.html'
], [
@ -698,7 +738,7 @@ credits = [
'https://raw.githubusercontent.com/ramda/ramda/master/LICENSE.txt'
], [
'React, React Native, Flow, Relay',
'2013-present Facebook Inc.',
'Facebook Inc. and its affiliates',
'MIT',
'https://raw.githubusercontent.com/facebook/react/master/LICENSE'
], [
@ -706,6 +746,11 @@ credits = [
'2014-present Stephen J. Collings, Matthew Honnibal, Pieter Vanderwerff',
'MIT',
'https://raw.githubusercontent.com/react-bootstrap/react-bootstrap/master/LICENSE'
], [
'React Router',
'React Training 2015-2019<br>Remix Software 2020-2022',
'MIT',
'https://raw.githubusercontent.com/remix-run/react-router/main/LICENSE.md'
], [
'ReactiveX',
'ReactiveX contributors',
@ -718,7 +763,7 @@ credits = [
'https://creativecommons.org/licenses/by-sa/4.0/'
], [
'Redux',
'2015-2020 Dan Abramov',
'2015-2022 Dan Abramov',
'MIT',
'https://raw.githubusercontent.com/reactjs/redux/master/LICENSE.md'
], [
@ -733,12 +778,12 @@ credits = [
'https://raw.githubusercontent.com/rethinkdb/docs/master/LICENSE'
], [
'Ruby',
'1993-2017 Yukihiro Matsumoto',
'1993-2022 Yukihiro Matsumoto',
'Ruby',
'https://www.ruby-lang.org/en/about/license.txt'
], [
'Ruby on Rails',
'2004-2020 David Heinemeier Hansson<br>Rails, Ruby on Rails, and the Rails logo are trademarks of David Heinemeier Hansson.',
'2004-2021 David Heinemeier Hansson<br>Rails, Ruby on Rails, and the Rails logo are trademarks of David Heinemeier Hansson.',
'MIT',
'https://raw.githubusercontent.com/rails/rails/master/activerecord/MIT-LICENSE'
], [
@ -748,7 +793,7 @@ credits = [
'https://raw.githubusercontent.com/rust-lang/book/master/LICENSE-MIT'
], [
'RxJS',
'2015-2021 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors',
'2015-2022 Google, Inc., Netflix, Inc., Microsoft Corp. and contributors',
'Apache',
'https://raw.githubusercontent.com/ReactiveX/rxjs/master/LICENSE.txt'
], [
@ -763,9 +808,9 @@ credits = [
'https://raw.githubusercontent.com/sass/sass/stable/MIT-LICENSE'
], [
'Scala',
'2002-2019 EPFL, with contributions from Lightbend',
'2002-2022 EPFL, with contributions from Lightbend',
'Apache',
'https://raw.githubusercontent.com/scala/scala-lang/master/license.md'
'https://www.scala-lang.org/license/'
], [
'scikit-image',
'2019 the scikit-image team',
@ -776,21 +821,26 @@ credits = [
'2007-2020 The scikit-learn developers',
'BSD',
'https://raw.githubusercontent.com/scikit-learn/scikit-learn/master/COPYING'
], [
'Sinon',
'2010-2021 Christian Johansen',
'BSD',
'https://raw.githubusercontent.com/sinonjs/sinon/master/LICENSE'
], [
'Sequelize',
'2014—present Sequelize contributors',
'MIT',
'https://raw.githubusercontent.com/sequelize/sequelize/master/LICENSE'
], [
'Sinon',
'2010-2021 Christian Johansen',
'BSD',
'https://raw.githubusercontent.com/sinonjs/sinon/master/LICENSE'
], [
'Socket.io',
'2014-2018 Automattic',
'MIT',
'https://raw.githubusercontent.com/Automattic/socket.io/master/LICENSE'
], [
'Spring Boot',
'2002-2022 Pivotal, Inc. All Rights Reserved.',
'Apache License 2.0',
'https://raw.githubusercontent.com/spring-projects/spring-boot/master/LICENSE.txt'
], [
'SQLite',
'n/a',
@ -806,6 +856,11 @@ credits = [
'2004-2017 Fabien Potencier',
'MIT',
'https://symfony.com/doc/current/contributing/code/license.html'
], [
'TailwindCSS',
'2022 Tailwind Labs, Inc.',
'MIT',
'https://raw.githubusercontent.com/tailwindlabs/tailwindcss/master/LICENSE'
], [
'Tcl/Tk',
'The Regents of the University of California, Sun Microsystems, Inc., Scriptics Corporation, and other parties',
@ -813,7 +868,7 @@ credits = [
'http://tcl.tk/software/tcltk/license.html'
], [
'TensorFlow',
'2020 The TensorFlow Authors',
'2022 The TensorFlow Authors',
'CC BY',
'https://creativecommons.org/licenses/by/4.0/'
], [
@ -833,7 +888,7 @@ credits = [
'https://twig.symfony.com/license'
], [
'TypeScript',
'2012-2021 Microsoft',
'2012-2022 Microsoft',
'Apache',
'https://raw.githubusercontent.com/Microsoft/TypeScript-Handbook/master/LICENSE'
], [
@ -846,6 +901,11 @@ credits = [
'2010-2018 Mitchell Hashimoto',
'MPL',
'https://raw.githubusercontent.com/mitchellh/vagrant/master/website/LICENSE.md'
], [
'Vite',
'2019present, Yuxi (Evan) You and Vite contributors',
'MIT',
'https://github.com/vitejs/vite/blob/main/LICENSE'
], [
'Vue Router',
'2013-present Evan You',
@ -873,12 +933,12 @@ credits = [
'https://creativecommons.org/licenses/by/4.0/'
], [
'Werkzeug',
'2007-2021 Pallets',
'2007-2022 Pallets',
'BSD',
'https://github.com/pallets/werkzeug/blob/master/LICENSE.rst'
], [
'Wordpress',
'2003-2021 WordPress Foundation',
'2003-2022 WordPress Foundation',
'GPLv2+',
'https://wordpress.org/about/license/'
], [
@ -892,9 +952,9 @@ credits = [
'BSD',
'https://raw.githubusercontent.com/yiisoft/yii/master/LICENSE'
], [
'Spring Boot',
'2002-2020 Pivotal, Inc. All Rights Reserved.',
'Apache License 2.0',
'https://raw.githubusercontent.com/spring-projects/spring-boot/master/LICENSE.txt'
'Zig',
'20152021, Zig contributors',
'MIT',
'https://raw.githubusercontent.com/ziglang/zig/master/LICENSE'
]
]

@ -36,7 +36,7 @@ app.templates.intro = """
<li>The search supports fuzzy matching (e.g. "bgcp" brings up "background-clip").
<li>To search a specific documentation, type its name (or an abbr.), then Tab.
<li>You can search using your browser's address bar &mdash; <a href="/help#browser_search">learn how</a>.
<li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed on <a href="https://chrome.google.com/webstore/detail/devdocs/mnfehgbmkapmjnhcnbodoamcioleeooe">Chrome</a>.
<li>DevDocs works <a href="/offline">offline</a>, on mobile, and can be installed as web app.
<li>For the latest news, follow <a href="https://twitter.com/DevDocs">@DevDocs</a>.
<li>DevDocs is free and <a href="https://github.com/freeCodeCamp/devdocs">open source</a>.
<object data="https://img.shields.io/github/stars/freeCodeCamp/devdocs.svg?style=social" type="image/svg+xml" aria-hidden="true" height="20"></object>

@ -27,6 +27,9 @@ app.templates.settingsPage = (settings) -> """
<label class="_settings-label _setting-max-width">
<input type="checkbox" form="settings" name="layout" value="_max-width"#{if settings['_max-width'] then ' checked' else ''}>Enable fixed-width layout
</label>
<label class="_settings-label _setting-text-justify-hyphenate">
<input type="checkbox" form="settings" name="layout" value="_text-justify-hyphenate"#{if settings['_text-justify-hyphenate'] then ' checked' else ''}>Enable justified layout and automatic hyphenation
</label>
<label class="_settings-label _hide-on-mobile">
<input type="checkbox" form="settings" name="layout" value="_sidebar-hidden"#{if settings['_sidebar-hidden'] then ' checked' else ''}>Automatically hide and show the sidebar
<small>Tip: drag the edge of the sidebar to resize it.</small>

@ -1,240 +0,0 @@
/*
* classList.js: Cross-browser full element.classList implementation.
* 1.1.20170427
*
* By Eli Grey, http://eligrey.com
* License: Dedicated to the public domain.
* See https://github.com/eligrey/classList.js/blob/master/LICENSE.md
*/
/*global self, document, DOMException */
/*! @source http://purl.eligrey.com/github/classList.js/blob/master/classList.js */
if ("document" in self) {
// Full polyfill for browsers with no classList support
// Including IE < Edge missing SVGElement.classList
if (!("classList" in document.createElement("_"))
|| document.createElementNS && !("classList" in document.createElementNS("http://www.w3.org/2000/svg","g"))) {
(function (view) {
"use strict";
if (!('Element' in view)) return;
var
classListProp = "classList"
, protoProp = "prototype"
, elemCtrProto = view.Element[protoProp]
, objCtr = Object
, strTrim = String[protoProp].trim || function () {
return this.replace(/^\s+|\s+$/g, "");
}
, arrIndexOf = Array[protoProp].indexOf || function (item) {
var
i = 0
, len = this.length
;
for (; i < len; i++) {
if (i in this && this[i] === item) {
return i;
}
}
return -1;
}
// Vendors: please allow content code to instantiate DOMExceptions
, DOMEx = function (type, message) {
this.name = type;
this.code = DOMException[type];
this.message = message;
}
, checkTokenAndGetIndex = function (classList, token) {
if (token === "") {
throw new DOMEx(
"SYNTAX_ERR"
, "An invalid or illegal string was specified"
);
}
if (/\s/.test(token)) {
throw new DOMEx(
"INVALID_CHARACTER_ERR"
, "String contains an invalid character"
);
}
return arrIndexOf.call(classList, token);
}
, ClassList = function (elem) {
var
trimmedClasses = strTrim.call(elem.getAttribute("class") || "")
, classes = trimmedClasses ? trimmedClasses.split(/\s+/) : []
, i = 0
, len = classes.length
;
for (; i < len; i++) {
this.push(classes[i]);
}
this._updateClassName = function () {
elem.setAttribute("class", this.toString());
};
}
, classListProto = ClassList[protoProp] = []
, classListGetter = function () {
return new ClassList(this);
}
;
// Most DOMException implementations don't allow calling DOMException's toString()
// on non-DOMExceptions. Error's toString() is sufficient here.
DOMEx[protoProp] = Error[protoProp];
classListProto.item = function (i) {
return this[i] || null;
};
classListProto.contains = function (token) {
token += "";
return checkTokenAndGetIndex(this, token) !== -1;
};
classListProto.add = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
;
do {
token = tokens[i] + "";
if (checkTokenAndGetIndex(this, token) === -1) {
this.push(token);
updated = true;
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.remove = function () {
var
tokens = arguments
, i = 0
, l = tokens.length
, token
, updated = false
, index
;
do {
token = tokens[i] + "";
index = checkTokenAndGetIndex(this, token);
while (index !== -1) {
this.splice(index, 1);
updated = true;
index = checkTokenAndGetIndex(this, token);
}
}
while (++i < l);
if (updated) {
this._updateClassName();
}
};
classListProto.toggle = function (token, force) {
token += "";
var
result = this.contains(token)
, method = result ?
force !== true && "remove"
:
force !== false && "add"
;
if (method) {
this[method](token);
}
if (force === true || force === false) {
return force;
} else {
return !result;
}
};
classListProto.toString = function () {
return this.join(" ");
};
if (objCtr.defineProperty) {
var classListPropDesc = {
get: classListGetter
, enumerable: true
, configurable: true
};
try {
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
} catch (ex) { // IE 8 doesn't support enumerable:true
// adding undefined to fight this issue https://github.com/eligrey/classList.js/issues/36
// modernie IE8-MSW7 machine has IE8 8.0.6001.18702 and is affected
if (ex.number === undefined || ex.number === -0x7FF5EC54) {
classListPropDesc.enumerable = false;
objCtr.defineProperty(elemCtrProto, classListProp, classListPropDesc);
}
}
} else if (objCtr[protoProp].__defineGetter__) {
elemCtrProto.__defineGetter__(classListProp, classListGetter);
}
}(self));
}
// There is full or partial native classList support, so just check if we need
// to normalize the add/remove and toggle APIs.
(function () {
"use strict";
var testElement = document.createElement("_");
testElement.classList.add("c1", "c2");
// Polyfill for IE 10/11 and Firefox <26, where classList.add and
// classList.remove exist but support only one argument at a time.
if (!testElement.classList.contains("c2")) {
var createMethod = function(method) {
var original = DOMTokenList.prototype[method];
DOMTokenList.prototype[method] = function(token) {
var i, len = arguments.length;
for (i = 0; i < len; i++) {
token = arguments[i];
original.call(this, token);
}
};
};
createMethod('add');
createMethod('remove');
}
testElement.classList.toggle("c3", false);
// Polyfill for IE 10 and Firefox <24, where classList.toggle does not
// support the second argument.
if (testElement.classList.contains("c3")) {
var _toggle = DOMTokenList.prototype.toggle;
DOMTokenList.prototype.toggle = function(token, force) {
if (1 in arguments && !this.contains(token) === !force) {
return force;
} else {
return _toggle.call(this, token);
}
};
}
testElement = null;
}());
}

@ -1,5 +1,5 @@
/*
* Cookies.js - 1.2.3
* Cookies.js - 1.2.3 (patched for SameSite=Strict and secure=true)
* https://github.com/ScottHamper/Cookies
*
* This is free and unencumbered software released into the public domain.
@ -28,7 +28,8 @@
Cookies.defaults = {
path: '/',
secure: false
SameSite: 'Strict',
secure: true
};
Cookies.get = function (key) {
@ -58,6 +59,7 @@
return {
path: options && options.path || Cookies.defaults.path,
domain: options && options.domain || Cookies.defaults.domain,
SameSite: options && options.SameSite || Cookies.defaults.SameSite,
expires: options && options.expires || Cookies.defaults.expires,
secure: options && options.secure !== undefined ? options.secure : Cookies.defaults.secure
};
@ -93,6 +95,7 @@
var cookieString = key + '=' + value;
cookieString += options.path ? ';path=' + options.path : '';
cookieString += options.domain ? ';domain=' + options.domain : '';
cookieString += options.SameSite ? ';SameSite=' + options.SameSite : '';
cookieString += options.expires ? ';expires=' + options.expires.toUTCString() : '';
cookieString += options.secure ? ';secure' : '';

@ -1,841 +0,0 @@
;(function () {
'use strict';
/**
* @preserve FastClick: polyfill to remove click delays on browsers with touch UIs.
*
* @codingstandard ftlabs-jsv2
* @copyright The Financial Times Limited [All Rights Reserved]
* @license MIT License (see LICENSE.txt)
*/
/*jslint browser:true, node:true*/
/*global define, Event, Node*/
/**
* Instantiate fast-clicking listeners on the specified layer.
*
* @constructor
* @param {Element} layer The layer to listen on
* @param {Object} [options={}] The options to override the defaults
*/
function FastClick(layer, options) {
var oldOnClick;
options = options || {};
/**
* Whether a click is currently being tracked.
*
* @type boolean
*/
this.trackingClick = false;
/**
* Timestamp for when click tracking started.
*
* @type number
*/
this.trackingClickStart = 0;
/**
* The element being tracked for a click.
*
* @type EventTarget
*/
this.targetElement = null;
/**
* X-coordinate of touch start event.
*
* @type number
*/
this.touchStartX = 0;
/**
* Y-coordinate of touch start event.
*
* @type number
*/
this.touchStartY = 0;
/**
* ID of the last touch, retrieved from Touch.identifier.
*
* @type number
*/
this.lastTouchIdentifier = 0;
/**
* Touchmove boundary, beyond which a click will be cancelled.
*
* @type number
*/
this.touchBoundary = options.touchBoundary || 10;
/**
* The FastClick layer.
*
* @type Element
*/
this.layer = layer;
/**
* The minimum time between tap(touchstart and touchend) events
*
* @type number
*/
this.tapDelay = options.tapDelay || 200;
/**
* The maximum time for a tap
*
* @type number
*/
this.tapTimeout = options.tapTimeout || 700;
if (FastClick.notNeeded(layer)) {
return;
}
// Some old versions of Android don't have Function.prototype.bind
function bind(method, context) {
return function() { return method.apply(context, arguments); };
}
var methods = ['onMouse', 'onClick', 'onTouchStart', 'onTouchMove', 'onTouchEnd', 'onTouchCancel'];
var context = this;
for (var i = 0, l = methods.length; i < l; i++) {
context[methods[i]] = bind(context[methods[i]], context);
}
// Set up event handlers as required
if (deviceIsAndroid) {
layer.addEventListener('mouseover', this.onMouse, true);
layer.addEventListener('mousedown', this.onMouse, true);
layer.addEventListener('mouseup', this.onMouse, true);
}
layer.addEventListener('click', this.onClick, true);
layer.addEventListener('touchstart', this.onTouchStart, false);
layer.addEventListener('touchmove', this.onTouchMove, false);
layer.addEventListener('touchend', this.onTouchEnd, false);
layer.addEventListener('touchcancel', this.onTouchCancel, false);
// Hack is required for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
// which is how FastClick normally stops click events bubbling to callbacks registered on the FastClick
// layer when they are cancelled.
if (!Event.prototype.stopImmediatePropagation) {
layer.removeEventListener = function(type, callback, capture) {
var rmv = Node.prototype.removeEventListener;
if (type === 'click') {
rmv.call(layer, type, callback.hijacked || callback, capture);
} else {
rmv.call(layer, type, callback, capture);
}
};
layer.addEventListener = function(type, callback, capture) {
var adv = Node.prototype.addEventListener;
if (type === 'click') {
adv.call(layer, type, callback.hijacked || (callback.hijacked = function(event) {
if (!event.propagationStopped) {
callback(event);
}
}), capture);
} else {
adv.call(layer, type, callback, capture);
}
};
}
// If a handler is already declared in the element's onclick attribute, it will be fired before
// FastClick's onClick handler. Fix this by pulling out the user-defined handler function and
// adding it as listener.
if (typeof layer.onclick === 'function') {
// Android browser on at least 3.2 requires a new reference to the function in layer.onclick
// - the old one won't work if passed to addEventListener directly.
oldOnClick = layer.onclick;
layer.addEventListener('click', function(event) {
oldOnClick(event);
}, false);
layer.onclick = null;
}
}
/**
* Windows Phone 8.1 fakes user agent string to look like Android and iPhone.
*
* @type boolean
*/
var deviceIsWindowsPhone = navigator.userAgent.indexOf("Windows Phone") >= 0;
/**
* Android requires exceptions.
*
* @type boolean
*/
var deviceIsAndroid = navigator.userAgent.indexOf('Android') > 0 && !deviceIsWindowsPhone;
/**
* iOS requires exceptions.
*
* @type boolean
*/
var deviceIsIOS = /iP(ad|hone|od)/.test(navigator.userAgent) && !deviceIsWindowsPhone;
/**
* iOS 4 requires an exception for select elements.
*
* @type boolean
*/
var deviceIsIOS4 = deviceIsIOS && (/OS 4_\d(_\d)?/).test(navigator.userAgent);
/**
* iOS 6.0-7.* requires the target element to be manually derived
*
* @type boolean
*/
var deviceIsIOSWithBadTarget = deviceIsIOS && (/OS [6-7]_\d/).test(navigator.userAgent);
/**
* BlackBerry requires exceptions.
*
* @type boolean
*/
var deviceIsBlackBerry10 = navigator.userAgent.indexOf('BB10') > 0;
/**
* Determine whether a given element requires a native click.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element needs a native click
*/
FastClick.prototype.needsClick = function(target) {
switch (target.nodeName.toLowerCase()) {
// Don't send a synthetic click to disabled inputs (issue #62)
case 'button':
case 'select':
case 'textarea':
if (target.disabled) {
return true;
}
break;
case 'input':
// File inputs need real clicks on iOS 6 due to a browser bug (issue #68)
if ((deviceIsIOS && target.type === 'file') || target.disabled) {
return true;
}
break;
case 'label':
case 'iframe': // iOS8 homescreen apps can prevent events bubbling into frames
case 'video':
return true;
}
return (/\bneedsclick\b/).test(target.className);
};
/**
* Determine whether a given element requires a call to focus to simulate click into element.
*
* @param {EventTarget|Element} target Target DOM element
* @returns {boolean} Returns true if the element requires a call to focus to simulate native click.
*/
FastClick.prototype.needsFocus = function(target) {
switch (target.nodeName.toLowerCase()) {
case 'textarea':
return true;
case 'select':
return !deviceIsAndroid;
case 'input':
switch (target.type) {
case 'button':
case 'checkbox':
case 'file':
case 'image':
case 'radio':
case 'submit':
return false;
}
// No point in attempting to focus disabled inputs
return !target.disabled && !target.readOnly;
default:
return (/\bneedsfocus\b/).test(target.className);
}
};
/**
* Send a click event to the specified element.
*
* @param {EventTarget|Element} targetElement
* @param {Event} event
*/
FastClick.prototype.sendClick = function(targetElement, event) {
var clickEvent, touch;
// On some Android devices activeElement needs to be blurred otherwise the synthetic click will have no effect (#24)
if (document.activeElement && document.activeElement !== targetElement) {
document.activeElement.blur();
}
touch = event.changedTouches[0];
// Synthesise a click event, with an extra attribute so it can be tracked
clickEvent = document.createEvent('MouseEvents');
clickEvent.initMouseEvent(this.determineEventType(targetElement), true, true, window, 1, touch.screenX, touch.screenY, touch.clientX, touch.clientY, false, false, false, false, 0, null);
clickEvent.forwardedTouchEvent = true;
targetElement.dispatchEvent(clickEvent);
};
FastClick.prototype.determineEventType = function(targetElement) {
//Issue #159: Android Chrome Select Box does not open with a synthetic click event
if (deviceIsAndroid && targetElement.tagName.toLowerCase() === 'select') {
return 'mousedown';
}
return 'click';
};
/**
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.focus = function(targetElement) {
var length;
// Issue #160: on iOS 7, some input elements (e.g. date datetime month) throw a vague TypeError on setSelectionRange. These elements don't have an integer value for the selectionStart and selectionEnd properties, but unfortunately that can't be used for detection because accessing the properties also throws a TypeError. Just check the type instead. Filed as Apple bug #15122724.
if (deviceIsIOS && targetElement.setSelectionRange && targetElement.type.indexOf('date') !== 0 && targetElement.type !== 'time' && targetElement.type !== 'month') {
length = targetElement.value.length;
targetElement.setSelectionRange(length, length);
} else {
targetElement.focus();
}
};
/**
* Check whether the given target element is a child of a scrollable layer and if so, set a flag on it.
*
* @param {EventTarget|Element} targetElement
*/
FastClick.prototype.updateScrollParent = function(targetElement) {
var scrollParent, parentElement;
scrollParent = targetElement.fastClickScrollParent;
// Attempt to discover whether the target element is contained within a scrollable layer. Re-check if the
// target element was moved to another parent.
if (!scrollParent || !scrollParent.contains(targetElement)) {
parentElement = targetElement;
do {
if (parentElement.scrollHeight > parentElement.offsetHeight) {
scrollParent = parentElement;
targetElement.fastClickScrollParent = parentElement;
break;
}
parentElement = parentElement.parentElement;
} while (parentElement);
}
// Always update the scroll top tracker if possible.
if (scrollParent) {
scrollParent.fastClickLastScrollTop = scrollParent.scrollTop;
}
};
/**
* @param {EventTarget} targetElement
* @returns {Element|EventTarget}
*/
FastClick.prototype.getTargetElementFromEventTarget = function(eventTarget) {
// On some older browsers (notably Safari on iOS 4.1 - see issue #56) the event target may be a text node.
if (eventTarget.nodeType === Node.TEXT_NODE) {
return eventTarget.parentNode;
}
return eventTarget;
};
/**
* On touch start, record the position and scroll offset.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchStart = function(event) {
var targetElement, touch, selection;
// Ignore multiple touches, otherwise pinch-to-zoom is prevented if both fingers are on the FastClick element (issue #111).
if (event.targetTouches.length > 1) {
return true;
}
targetElement = this.getTargetElementFromEventTarget(event.target);
touch = event.targetTouches[0];
if (deviceIsIOS) {
// Only trusted events will deselect text on iOS (issue #49)
selection = window.getSelection();
if (selection.rangeCount && !selection.isCollapsed) {
return true;
}
if (!deviceIsIOS4) {
// Weird things happen on iOS when an alert or confirm dialog is opened from a click event callback (issue #23):
// when the user next taps anywhere else on the page, new touchstart and touchend events are dispatched
// with the same identifier as the touch event that previously triggered the click that triggered the alert.
// Sadly, there is an issue on iOS 4 that causes some normal touch events to have the same identifier as an
// immediately preceding touch event (issue #52), so this fix is unavailable on that platform.
// Issue 120: touch.identifier is 0 when Chrome dev tools 'Emulate touch events' is set with an iOS device UA string,
// which causes all touch events to be ignored. As this block only applies to iOS, and iOS identifiers are always long,
// random integers, it's safe to to continue if the identifier is 0 here.
if (touch.identifier && touch.identifier === this.lastTouchIdentifier) {
event.preventDefault();
return false;
}
this.lastTouchIdentifier = touch.identifier;
// If the target element is a child of a scrollable layer (using -webkit-overflow-scrolling: touch) and:
// 1) the user does a fling scroll on the scrollable layer
// 2) the user stops the fling scroll with another tap
// then the event.target of the last 'touchend' event will be the element that was under the user's finger
// when the fling scroll was started, causing FastClick to send a click event to that layer - unless a check
// is made to ensure that a parent layer was not scrolled before sending a synthetic click (issue #42).
this.updateScrollParent(targetElement);
}
}
this.trackingClick = true;
this.trackingClickStart = event.timeStamp;
this.targetElement = targetElement;
this.touchStartX = touch.pageX;
this.touchStartY = touch.pageY;
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
event.preventDefault();
}
return true;
};
/**
* Based on a touchmove event object, check whether the touch has moved past a boundary since it started.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.touchHasMoved = function(event) {
var touch = event.changedTouches[0], boundary = this.touchBoundary;
if (Math.abs(touch.pageX - this.touchStartX) > boundary || Math.abs(touch.pageY - this.touchStartY) > boundary) {
return true;
}
return false;
};
/**
* Update the last position.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchMove = function(event) {
if (!this.trackingClick) {
return true;
}
// If the touch has moved, cancel the click tracking
if (this.targetElement !== this.getTargetElementFromEventTarget(event.target) || this.touchHasMoved(event)) {
this.trackingClick = false;
this.targetElement = null;
}
return true;
};
/**
* Attempt to find the labelled control for the given label element.
*
* @param {EventTarget|HTMLLabelElement} labelElement
* @returns {Element|null}
*/
FastClick.prototype.findControl = function(labelElement) {
// Fast path for newer browsers supporting the HTML5 control attribute
if (labelElement.control !== undefined) {
return labelElement.control;
}
// All browsers under test that support touch events also support the HTML5 htmlFor attribute
if (labelElement.htmlFor) {
return document.getElementById(labelElement.htmlFor);
}
// If no for attribute exists, attempt to retrieve the first labellable descendant element
// the list of which is defined here: http://www.w3.org/TR/html5/forms.html#category-label
return labelElement.querySelector('button, input:not([type=hidden]), keygen, meter, output, progress, select, textarea');
};
/**
* On touch end, determine whether to send a click event at once.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onTouchEnd = function(event) {
var forElement, trackingClickStart, targetTagName, scrollParent, touch, targetElement = this.targetElement;
if (!this.trackingClick) {
return true;
}
// Prevent phantom clicks on fast double-tap (issue #36)
if ((event.timeStamp - this.lastClickTime) < this.tapDelay) {
this.cancelNextClick = true;
return true;
}
if ((event.timeStamp - this.trackingClickStart) > this.tapTimeout) {
return true;
}
// Reset to prevent wrong click cancel on input (issue #156).
this.cancelNextClick = false;
this.lastClickTime = event.timeStamp;
trackingClickStart = this.trackingClickStart;
this.trackingClick = false;
this.trackingClickStart = 0;
// On some iOS devices, the targetElement supplied with the event is invalid if the layer
// is performing a transition or scroll, and has to be re-detected manually. Note that
// for this to function correctly, it must be called *after* the event target is checked!
// See issue #57; also filed as rdar://13048589 .
if (deviceIsIOSWithBadTarget) {
touch = event.changedTouches[0];
// In certain cases arguments of elementFromPoint can be negative, so prevent setting targetElement to null
targetElement = document.elementFromPoint(touch.pageX - window.pageXOffset, touch.pageY - window.pageYOffset) || targetElement;
targetElement.fastClickScrollParent = this.targetElement.fastClickScrollParent;
}
targetTagName = targetElement.tagName.toLowerCase();
if (targetTagName === 'label') {
forElement = this.findControl(targetElement);
if (forElement) {
this.focus(targetElement);
if (deviceIsAndroid) {
return false;
}
targetElement = forElement;
}
} else if (this.needsFocus(targetElement)) {
// Case 1: If the touch started a while ago (best guess is 100ms based on tests for issue #36) then focus will be triggered anyway. Return early and unset the target element reference so that the subsequent click will be allowed through.
// Case 2: Without this exception for input elements tapped when the document is contained in an iframe, then any inputted text won't be visible even though the value attribute is updated as the user types (issue #37).
if ((event.timeStamp - trackingClickStart) > 100 || (deviceIsIOS && window.top !== window && targetTagName === 'input')) {
this.targetElement = null;
return false;
}
this.focus(targetElement);
this.sendClick(targetElement, event);
// Select elements need the event to go through on iOS 4, otherwise the selector menu won't open.
// Also this breaks opening selects when VoiceOver is active on iOS6, iOS7 (and possibly others)
if (!deviceIsIOS || targetTagName !== 'select') {
this.targetElement = null;
event.preventDefault();
}
return false;
}
if (deviceIsIOS && !deviceIsIOS4) {
// Don't send a synthetic click event if the target element is contained within a parent layer that was scrolled
// and this tap is being used to stop the scrolling (usually initiated by a fling - issue #42).
scrollParent = targetElement.fastClickScrollParent;
if (scrollParent && scrollParent.fastClickLastScrollTop !== scrollParent.scrollTop) {
return true;
}
}
// Prevent the actual click from going though - unless the target node is marked as requiring
// real clicks or if it is in the whitelist in which case only non-programmatic clicks are permitted.
if (!this.needsClick(targetElement)) {
event.preventDefault();
this.sendClick(targetElement, event);
}
return false;
};
/**
* On touch cancel, stop tracking the click.
*
* @returns {void}
*/
FastClick.prototype.onTouchCancel = function() {
this.trackingClick = false;
this.targetElement = null;
};
/**
* Determine mouse events which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onMouse = function(event) {
// If a target element was never set (because a touch event was never fired) allow the event
if (!this.targetElement) {
return true;
}
if (event.forwardedTouchEvent) {
return true;
}
// Programmatically generated events targeting a specific element should be permitted
if (!event.cancelable) {
return true;
}
// Derive and check the target element to see whether the mouse event needs to be permitted;
// unless explicitly enabled, prevent non-touch click events from triggering actions,
// to prevent ghost/doubleclicks.
if (!this.needsClick(this.targetElement) || this.cancelNextClick) {
// Prevent any user-added listeners declared on FastClick element from being fired.
if (event.stopImmediatePropagation) {
event.stopImmediatePropagation();
} else {
// Part of the hack for browsers that don't support Event#stopImmediatePropagation (e.g. Android 2)
event.propagationStopped = true;
}
// Cancel the event
event.stopPropagation();
event.preventDefault();
return false;
}
// If the mouse event is permitted, return true for the action to go through.
return true;
};
/**
* On actual clicks, determine whether this is a touch-generated click, a click action occurring
* naturally after a delay after a touch (which needs to be cancelled to avoid duplication), or
* an actual click which should be permitted.
*
* @param {Event} event
* @returns {boolean}
*/
FastClick.prototype.onClick = function(event) {
var permitted;
// It's possible for another FastClick-like library delivered with third-party code to fire a click event before FastClick does (issue #44). In that case, set the click-tracking flag back to false and return early. This will cause onTouchEnd to return early.
if (this.trackingClick) {
this.targetElement = null;
this.trackingClick = false;
return true;
}
// Very odd behaviour on iOS (issue #18): if a submit element is present inside a form and the user hits enter in the iOS simulator or clicks the Go button on the pop-up OS keyboard the a kind of 'fake' click event will be triggered with the submit-type input element as the target.
if (event.target.type === 'submit' && event.detail === 0) {
return true;
}
permitted = this.onMouse(event);
// Only unset targetElement if the click is not permitted. This will ensure that the check for !targetElement in onMouse fails and the browser's click doesn't go through.
if (!permitted) {
this.targetElement = null;
}
// If clicks are permitted, return true for the action to go through.
return permitted;
};
/**
* Remove all FastClick's event listeners.
*
* @returns {void}
*/
FastClick.prototype.destroy = function() {
var layer = this.layer;
if (deviceIsAndroid) {
layer.removeEventListener('mouseover', this.onMouse, true);
layer.removeEventListener('mousedown', this.onMouse, true);
layer.removeEventListener('mouseup', this.onMouse, true);
}
layer.removeEventListener('click', this.onClick, true);
layer.removeEventListener('touchstart', this.onTouchStart, false);
layer.removeEventListener('touchmove', this.onTouchMove, false);
layer.removeEventListener('touchend', this.onTouchEnd, false);
layer.removeEventListener('touchcancel', this.onTouchCancel, false);
};
/**
* Check whether FastClick is needed.
*
* @param {Element} layer The layer to listen on
*/
FastClick.notNeeded = function(layer) {
var metaViewport;
var chromeVersion;
var blackberryVersion;
var firefoxVersion;
// Devices that don't support touch don't need FastClick
if (typeof window.ontouchstart === 'undefined') {
return true;
}
// Chrome version - zero for other browsers
chromeVersion = +(/Chrome\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
if (chromeVersion) {
if (deviceIsAndroid) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport) {
// Chrome on Android with user-scalable="no" doesn't need FastClick (issue #89)
if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
// Chrome 32 and above with width=device-width or less don't need FastClick
if (chromeVersion > 31 && document.documentElement.scrollWidth <= window.outerWidth) {
return true;
}
}
// Chrome desktop doesn't need FastClick (issue #15)
} else {
return true;
}
}
if (deviceIsBlackBerry10) {
blackberryVersion = navigator.userAgent.match(/Version\/([0-9]*)\.([0-9]*)/);
// BlackBerry 10.3+ does not require Fastclick library.
// https://github.com/ftlabs/fastclick/issues/251
if (blackberryVersion[1] >= 10 && blackberryVersion[2] >= 3) {
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport) {
// user-scalable=no eliminates click delay.
if (metaViewport.content.indexOf('user-scalable=no') !== -1) {
return true;
}
// width=device-width (or less than device-width) eliminates click delay.
if (document.documentElement.scrollWidth <= window.outerWidth) {
return true;
}
}
}
}
// IE10 with -ms-touch-action: none or manipulation, which disables double-tap-to-zoom (issue #97)
if (layer.style.msTouchAction === 'none' || layer.style.touchAction === 'manipulation') {
return true;
}
// Firefox version - zero for other browsers
firefoxVersion = +(/Firefox\/([0-9]+)/.exec(navigator.userAgent) || [,0])[1];
if (firefoxVersion >= 27) {
// Firefox 27+ does not have tap delay if the content is not zoomable - https://bugzilla.mozilla.org/show_bug.cgi?id=922896
metaViewport = document.querySelector('meta[name=viewport]');
if (metaViewport && (metaViewport.content.indexOf('user-scalable=no') !== -1 || document.documentElement.scrollWidth <= window.outerWidth)) {
return true;
}
}
// IE11: prefixed -ms-touch-action is no longer supported and it's recommended to use non-prefixed version
// http://msdn.microsoft.com/en-us/library/windows/apps/Hh767313.aspx
if (layer.style.touchAction === 'none' || layer.style.touchAction === 'manipulation') {
return true;
}
return false;
};
/**
* Factory method for creating a FastClick object
*
* @param {Element} layer The layer to listen on
* @param {Object} [options={}] The options to override the defaults
*/
FastClick.attach = function(layer, options) {
return new FastClick(layer, options);
};
if (typeof define === 'function' && typeof define.amd === 'object' && define.amd) {
// AMD. Register as an anonymous module.
define(function() {
return FastClick;
});
} else if (typeof module !== 'undefined' && module.exports) {
module.exports = FastClick.attach;
module.exports.FastClick = FastClick;
} else {
window.FastClick = FastClick;
}
}());

File diff suppressed because one or more lines are too long

@ -2222,7 +2222,7 @@ function urlencode(o) {
return pairs.join('&');
}
// borrowed from https://tools.ietf.org/html/rfc3986#appendix-B
// borrowed from https://datatracker.ietf.org/doc/html/rfc3986#appendix-B
// intentionally using regex and not <a/> href parsing trick because React Native and other
// environments where DOM might not be available
function parseUrl(url) {

@ -38,8 +38,6 @@ class app.views.Mobile extends app.View
super
init: ->
window.FastClick?.attach @body
$.on $('._search'), 'touchend', @onTapSearch
@toggleSidebar = $('button[data-toggle-sidebar]')

@ -52,6 +52,7 @@
'pages/dart',
'pages/dojo',
'pages/drupal',
'pages/eigen3',
'pages/elixir',
'pages/elisp',
'pages/ember',
@ -66,16 +67,20 @@
'pages/gtk',
'pages/haproxy',
'pages/haskell',
'pages/jasmine',
'pages/jekyll',
'pages/jq',
'pages/jquery',
'pages/julia',
'pages/knockout',
'pages/kotlin',
'pages/kubectl',
'pages/kubernetes',
'pages/laravel',
'pages/liquid',
'pages/love',
'pages/lua',
'pages/gnu_make',
'pages/mariadb',
'pages/mdn',
'pages/meteor',
@ -114,6 +119,7 @@
'pages/sphinx_simple',
'pages/sqlite',
'pages/support_tables',
'pages/tailwindcss',
'pages/tcl_tk',
'pages/tensorflow',
'pages/terraform',

@ -33,6 +33,11 @@
margin-top: var(--headerHeight);
}
._text-justify-hyphenate & {
text-align: justify;
hyphens: auto;
}
._overlay-scrollbars & { padding-left: .625rem; }
@media screen and (-ms-high-contrast: active), (-ms-high-contrast: none) { margin-left: 0; }
@supports (-ms-overflow-style: none) { margin-left: 0; }
@ -393,7 +398,12 @@
padding: .375rem;
cursor: pointer;
pre:hover > & { display: block; }
pre:hover > & {
display: block;
top: 0.1875rem;
padding: 0;
}
&:hover { opacity: 1; }
> svg {

@ -62,7 +62,7 @@
top: .25rem;
right: .25rem;
width: 8.5rem;
height: calc(13.75rem + 1px);
height: calc(2.25rem * 6 + 2.5rem + 1px); // (height of each menu element * total menu elements + menu title element total height + menu title border size)
white-space: nowrap;
word-wrap: normal;
overflow-wrap: normal;

@ -0,0 +1,5 @@
._gnu_make {
dl dt {
@extend %block-label, %label-blue;
}
}

@ -3,7 +3,7 @@
#short-nav, table.dir { margin-left: -1rem; }
a.source {
a.source, span[title^="Added in Go"] {
float: right;
font-size: .9em;
}

@ -0,0 +1,4 @@
._jasmine {
.subsection-title, h2 { @extend %block-heading; }
h4 { @extend %block-label, %label-blue; }
}

@ -0,0 +1,4 @@
._kubectl {
@extend %simple;
}

@ -0,0 +1,4 @@
._kubernetes {
@extend %simple;
}

@ -107,7 +107,7 @@
code > strong { font-weight: normal; }
// Compatibility tablees
// Compatibility tables
.bc-github-link {
float: right;
@ -115,6 +115,7 @@
}
.bc-supports-yes, .bc-supports-yes + dd, .bc-supports-yes + dd + dd { background: var(--noteGreenBackground); }
.bc-supports-unknown, .bc-supports-unknown + dd, .bc-supports-unknown + dd + dd { background: var(--noteBackground); }
.bc-supports-partial, .bc-supports-partial + dd, .bc-supports-partial + dd + dd { background: var(--noteOrangeBackground); }
.bc-supports-no, .bc-supports-no + dd, .bc-supports-no + dd + dd { background: var(--noteRedBackground); }
@ -130,4 +131,8 @@
dd { margin: 0; }
}
iframe.interactive {
width: 100%;
}
}

@ -1,5 +1,6 @@
._openjdk {
@extend %simple;
> .inheritance { @extend %note; }
ul.inheritance { list-style: none; }
> ul.inheritance { @extend %note, %note-blue; }

@ -6,4 +6,15 @@
span.platform { float: right; }
span.propType, span.platform { font-weight: normal; }
.label {
display:inline-block;
font-size:.85rem;
}
.label::before {
content: "[";
}
.label::after {
content: "]";
}
}

@ -2,6 +2,7 @@
@extend %simple;
h4 { @extend %block-label; }
.code-header { @extend %code; }
.docblock { margin-left: 1em; }
div.information, div.important-traits {
@extend %note;

@ -1,4 +1,43 @@
._scala {
@extend %simple;
.deprecated { @extend %label-red; }
.attributes dl,
.attributes pre {
margin: 0;
}
.related-types {
@extend %pre;
margin: 0;
white-space: normal;
}
.links {
@extend %box;
margin-left: -1rem;
text-align: center;
padding: .5em;
a { padding: .4em }
@include print {
display: none;
}
}
.source-link {
float: right;
font-size: .75rem;
color: var(--linkColor);
cursor: pointer;
@extend %user-select-none;
&:hover { text-decoration: underline; }
@include print {
display: none;
}
}
}

@ -39,6 +39,12 @@
.admonition-title + dl { padding-top: .5em; }
td > div { margin: 0 !important; }
.classifier:before { content:": " }
.property::after { content:" " }
span.descclassname, span.descname { font-family: var(--monoFont) }
}
._sphinx {

@ -0,0 +1,48 @@
._tailwindcss {
// Styling for customizing-colors page - color swatches (based on original tailwind display design)
.colors {
display: flex;
flex-direction: column;
gap: 1.2rem;
// Text offset
margin-bottom: 1rem;
}
// Color swatch title
.color > div:first-child {
font-weight: bold;
}
.color-swatch-group {
display: flex;
gap: 1rem;
flex-wrap: wrap;
}
.color-swatch-container {
display: inline-block;
}
// Tiny box with the color set as background
.color-swatch {
width: 120px;
height: 40px;
border-radius: 4px;
}
.color-tone-information {
display: flex;
justify-content: space-between;
}
// Styling for large quick-reference lookup tables
.long-quick-reference {
max-height: 40vh;
width: fit-content;
overflow-y: auto;
padding: .3rem;
border-top: 1px solid var(--textColor);
border-bottom: 1px solid var(--textColor);
}
}

@ -39,7 +39,7 @@ bsdtar --extract --file - --directory=docs/django\~$VERSION/
## Elisp
Go to https://www.gnu.org/software/emacs/manual/elisp.html, donwload the HTML tarball and extract its content in `/path/to/devdocs/docs/elisp` or run the following command:
Go to https://www.gnu.org/software/emacs/manual/elisp.html, download the HTML tarball and extract its content in `/path/to/devdocs/docs/elisp` or run the following command:
```sh
mkdir /path/to/devdocs/docs/elisp \
@ -59,6 +59,15 @@ bsdtar --extract --file - --directory=docs/erlang\~$VERSION/
## Gnu
### Bash
Go to https://www.gnu.org/software/bash/manual/, download the HTML tar file (with one web page per node) and extract its content in `/path/to/devdocs/docs/bash` or run the following command:
```sh
mkdir /path/to/devdocs/docs/bash \
&& curl https://www.gnu.org/software/bash/manual/bash.html_node.tar.gz | \
tar --extract --gzip --directory=/path/to/devdocs/docs/bash
```
### GCC
Go to https://gcc.gnu.org/onlinedocs/ and download the HTML tarball of GCC Manual and GCC CPP manual or run the following commands to download the tarballs:
@ -83,6 +92,15 @@ curl https://gcc.gnu.org/onlinedocs/gcc-$RELEASE/gfortran-html.tar.gz | \
tar --extract --gzip --strip-components=1 --directory=docs/gnu_fortran~$VERSION
```
## GNU Make
Go to https://www.gnu.org/software/make/manual/, download the HTML tarball and extract its content in `/path/to/devdocs/docs/gnu_make` or run the following command:
```sh
mkdir /path/to/devdocs/docs/gnu_make \
&& curl https://www.gnu.org/software/make/manual/make.html_node.tar.gz | \
tar --extract --gzip --strip-components=1 --directory=/path/to/devdocs/docs/gnu_make
```
## Gnuplot
The most recent release can be found near the bottom of
@ -154,14 +172,20 @@ dpkg -x $PACKAGE ./
mv ./usr/share/doc/openjdk-16-jre-headless/api/ path/to/devdocs/docs/openjdk~$VERSION
```
## Pandas
```sh
curl https://pandas.pydata.org/docs/pandas.zip | bsdtar --extract --file - --directory=docs/pandas~1
```
## PHP
Click the link under the "Many HTML files" column on https://www.php.net/download-docs.php, extract the tarball, change its name to `php` and put it in `/path/to/devdocs/docs/`.
Click the link under the "Many HTML files" column on https://www.php.net/download-docs.php, extract the tarball, change its name to `php` and put it in `docs/`.
Or run the following commands in your terminal:
```sh
curl https://www.php.net/distributions/manual/php_manual_en.tar.gz > php.tar; \
tar -xf php.tar; mv php-chunked-xhtml/ path/to/devdocs/docs/php/
tar -xf php.tar; mv php-chunked-xhtml/ docs/php/
```
## Python 3.6+
@ -208,6 +232,13 @@ done
### Nokogiri
### Ruby / Minitest
### Ruby on Rails
* Download a release at https://github.com/rails/rails/releases or clone https://github.com/rails/rails.git (checkout to the branch of the rails' version that is going to be scraped)
* Open "railties/lib/rails/api/task.rb" and comment out any code related to sdoc ("configure_sdoc")
* Run "bundle install --without db && bundle exec rake rdoc" (in the Rails directory)
* Run "cd guides && bundle exec rake guides:generate:html"
* Copy the "guides/output" directory to "html/guides"
* Copy the "html" directory to "docs/rails~[version]"
### Ruby
Download the tarball of Ruby from https://www.ruby-lang.org/en/downloads/, extract it, run
`./configure && make html` in your terminal (while your are in the ruby directory) and move
@ -230,3 +261,7 @@ See `lib/docs/scrapers/scala.rb`
Download the docs from https://sqlite.org/download.html, unzip it, and rename
it to `/path/to/devdocs/docs/sqlite`
```sh
curl https://sqlite.org/2022/sqlite-doc-3380000.zip | bsdtar --extract --file - --directory=docs/sqlite/ --strip-components=1
```

@ -60,6 +60,8 @@ Configuration is done via class attributes and divided into three main categorie
Defaults to `localhost` in `FileScraper`. _(Note: any iframe, image, or skipped link pointing to localhost will be removed by the `CleanLocalUrls` filter; the value should be overridden if the documents are available online.)_
Unless `root_path` is set, the root/initial URL is equal to `base_url`.
* `base_urls` [Array] **(the `MultipleBaseUrls` module must be included)** Documentation's locations. Almost the same as `base_url` but in this case more than one URL can be added, should be used when a documentation is split in different URLs or needs more URLs to be completed. See [`typescript.rb`](https://github.com/freeCodeCamp/devdocs/blob/main/lib/docs/scrapers/typescript.rb).
* `root_path` [String] **(inherited)**
The path from the `base_url` of the root URL.
@ -185,6 +187,30 @@ More information about how filters work is available on the [Filter Reference](.
_Note: this filter is disabled by default._
### Processing responses before filters
These methods are runned before filter stacks, and can directly process responses.
* `process_response?(response)`
Determine whether a response should be processed. A response will be dropped if this method returns `false`.
It is useful to filter pages, such as empty, invalid, or redirecting pages, depending on the content.
Example: [lib/docs/scrapers/kotlin.rb](../lib/docs/scrapers/kotlin.rb)
* `parse(response)`
Parse HTTP/File response, and convert to a Nokogiri document by default.
Overrides this method if you want to modified HTML source code before Nokogiri.
It is useful to preserve whitespaces of code segments within non-pre blocks, because Nokogiri may delete them.
Example: [lib/docs/scrapers/go.rb](../lib/docs/scrapers/go.rb)
## Keeping scrapers up-to-date
In order to keep scrapers up-to-date the `get_latest_version(opts)` method should be overridden. If `self.release` is defined, this should return the latest version of the documentation. If `self.release` is not defined, it should return the Epoch time when the documentation was last modified. If the documentation will never change, simply return `1.0.0`. The result of this method is periodically reported in a "Documentation versions report" issue which helps maintainers keep track of outdated documentations.

@ -146,10 +146,8 @@ class App < Sinatra::Application
@browser ||= Browser.new(request.user_agent)
end
UNSUPPORTED_IE_VERSIONS = %w(6 7 8 9).freeze
def unsupported_browser?
browser.ie? && UNSUPPORTED_IE_VERSIONS.include?(browser.version)
browser.ie?
end
def docs

@ -87,8 +87,15 @@ module Docs
end
def store_page(store, id)
index = EntryIndex.new
pages = PageDb.new
store.open(path) do
if page = new.build_page(id) and store_page?(page)
index.add page[:entries]
pages.add page[:path], page[:output]
store_index(store, INDEX_FILENAME, index, false)
store_index(store, DB_FILENAME, pages, false)
store.write page[:store_path], page[:output]
true
else
@ -137,11 +144,11 @@ module Docs
page[:entries].present?
end
def store_index(store, filename, index)
old_json = store.read(filename) || '{}'
def store_index(store, filename, index, read_write=true)
old_json = read_write && store.read(filename) || '{}'
new_json = index.to_json
instrument "#{filename.remove('.json')}.doc", before: old_json, after: new_json
store.write(filename, new_json)
read_write && store.write(filename, new_json)
end
def store_meta(store)
@ -203,6 +210,8 @@ module Docs
[0, 1].each do |i|
break if i >= scraper_parts.length or i >= latest_parts.length
return 'Outdated major version' if i == 0 and latest_parts[i] > scraper_parts[i]
return 'Outdated major version' if i == 1 and latest_parts[i] > scraper_parts[i] and latest_parts[0] == 0 and scraper_parts[0] == 0
return 'Outdated major version' if i == 1 and latest_parts[i] > scraper_parts[i] and latest_parts[0] == 1 and scraper_parts[0] == 1
return 'Outdated minor version' if i == 1 and latest_parts[i] > scraper_parts[i]
return 'Up-to-date' if latest_parts[i] < scraper_parts[i]
end

@ -4,11 +4,12 @@ module Docs
def get_name
name = at_css('h1').content.strip
name.remove! "\u{00B6}"
name.remove! "\u{f0c1}"
name.remove! %r{ \- .*}
name.remove! 'Introduction To '
name.remove! %r{ Guide\z}
if version == "2.10"
if version >= "2.10" || version == ""
if slug =~ /\Acollections\// and slug !~ /index$/
name = name.split('.')[2]
end
@ -28,7 +29,7 @@ module Docs
end
end
if version >= "2.10"
if version >= "2.10" || version == ""
if slug =~ /\Acollections\//
return "Collection #{slug.split('/')[1..-2].join(".")}"
end

@ -4,6 +4,8 @@ module Docs
def call
@doc = at_css('main')
at_css('.bd-content').prepend_child(at_css('h1').remove)
@doc = at_css('.bd-content')
# Toc
css('.bd-toc').remove

@ -20,7 +20,7 @@ module Docs
entries = []
# titles
css('h2', 'h3').each do |node|
css('h2:not(.accordion-header)', 'h3').each do |node|
entries << [ name + ': ' + node.content, node['id']]
end

@ -42,6 +42,8 @@ module Docs
node.parent.content = node.parent.content
end
css('.alert-warning').remove
doc
end
end

@ -38,7 +38,7 @@ module Docs
path = path.downcase
if context[:decode_and_clean_paths]
path = URI.unescape(path)
path = CGI.unescape(path)
path = clean_path(path)
end

@ -2,7 +2,7 @@ module Docs
class Crystal
class CleanHtmlFilter < Filter
def call
slug.start_with?('reference') ? book : api
current_url.path.start_with?('/reference/') ? book : api
doc
end
@ -21,7 +21,7 @@ module Docs
def api
@doc = at_css('.main-content')
at_css('h1 + p').remove if root_page?
at_css('h1 + p').remove if current_url.path == "/api/#{release}/index.html"
css('.method-permalink', '.doc + br', 'hr', 'a > br', 'div + br').remove

@ -2,11 +2,11 @@ module Docs
class Crystal
class EntriesFilter < Docs::EntriesFilter
def get_name
if slug.start_with?('reference/')
if current_url.path.start_with?('/reference/')
name = at_css('main h1').content.strip
name.remove! '¶'
if slug.start_with?('reference/syntax_and_semantics')
if current_url.path.start_with?('/reference/') && slug.match?('syntax_and_semantics')
name.prepend "#{slug.split('/')[2].titleize}: " if slug.split('/').length > 3
elsif slug.split('/').length > 1
chapter = slug.split('/')[1].titleize.capitalize
@ -15,6 +15,7 @@ module Docs
name
else
return at_css('h1').content.strip unless at_css('.type-name')
name = at_css('.type-name').children.last.content.strip
name.remove! %r{\(.*\)}
name
@ -22,11 +23,11 @@ module Docs
end
def get_type
return if root_page?
return if current_url.path == "/api/#{release}/index.html"
if slug.start_with?('reference/syntax_and_semantics')
if current_url.path.start_with?('/reference/') && slug.match?('syntax_and_semantics')
'Book: Language'
elsif slug.start_with?('reference/')
elsif current_url.path.start_with?('/reference/')
'Book'
else
hierarchy = at_css('.superclass-hierarchy')
@ -44,7 +45,7 @@ module Docs
end
def additional_entries
return [] unless slug.start_with?('api')
return [] unless current_url.path.start_with?('/api/')
entries = []
css('.entry-detail[id$="class-method"]').each do |node|

@ -0,0 +1,22 @@
module Docs
class Deno
class CleanHtmlFilter < Filter
def call
if root_page?
@doc = at_css('h2[id]').parent.parent
else
@doc = at_css('article')
end
css('*[aria-label="Anchor"]').remove
css('*[class]').remove_attribute('class')
css('pre').each do |node|
node['data-language'] = 'typescript'
end
xpath('//a[text()="[src]"]').remove
doc
end
end
end
end

@ -0,0 +1,15 @@
module Docs
class Deno
class EntriesFilter < Docs::EntriesFilter
def get_name
at_css('h1').content
end
def get_type
'Deno CLI APIs'
end
end
end
end

@ -1,37 +0,0 @@
module Docs
class Docker
class CleanHtmlOldFilter < Filter
def call
if root_page?
doc.inner_html = "<h1>Docker Documentation</h1>"
return doc
end
@doc = at_css('#DocumentationText')
at_css('h2').name = 'h1' unless at_css('h1')
css('.anchorLink', '.reading-time', 'hr', '> div[style*="margin-top"]:last-child').remove
css('h1 + h1').each do |node|
node.name = 'h2'
end
css('pre').each do |node|
node.content = node.content
node['data-language'] = node.parent['class'][/language-(\w+)/, 1] if node.parent['class']
end
css('div.highlighter-rouge').each do |node|
node.before(node.children).remove
end
css('code.highlighter-rouge').each do |node|
node.content = node.content.gsub(/\s+/, ' ').strip
end
doc
end
end
end
end

@ -1,22 +0,0 @@
module Docs
class Docker
class CleanHtmlVeryOldFilter < Filter
def call
if root_page?
doc.inner_html = "<h1>Docker Documentation</h1>"
return doc
end
@doc = at_css('#content')
at_css('h2').name = 'h1' unless at_css('h1')
css('pre').each do |node|
node.content = node.content
end
doc
end
end
end
end

@ -10,20 +10,7 @@ module Docs
def get_name
return NAME_BY_SUBPATH[subpath] if NAME_BY_SUBPATH[subpath]
return at_css('h1').content unless nav_link
name = nav_link.content.strip
name.capitalize! if name == 'exoscale'
name.remove! ' (base command)'
if name =~ /\A[a-z\-\s]+\z/
name.prepend 'docker-compose ' if subpath =~ /compose\/reference\/./
name.prepend 'docker-machine ' if subpath =~ /machine\/reference\/./
else
name << " (#{product})" if name !~ /#{product}/i && !subpath.start_with?('get-started')
end
name
at_css('h1').content
end
def get_type
@ -37,11 +24,6 @@ module Docs
product
end
def nav_link
return @nav_link if defined?(@nav_link)
@nav_link = at_css('.currentPage')
end
def product
@product ||= subpath.split('/').first.capitalize
end

@ -1,75 +0,0 @@
module Docs
class Docker
class EntriesOldFilter < Docs::EntriesFilter
NAME_BY_SUBPATH = {
'engine/' => 'Engine',
'compose/' => 'Compose',
'machine/' => 'Machine'
}
def get_name
return NAME_BY_SUBPATH[subpath] if NAME_BY_SUBPATH[subpath]
return at_css('h1').content unless nav_link
name = nav_link.content.strip
name.capitalize! if name == 'exoscale'
name.remove! ' (base command)'
if name =~ /\A[a-z\-\s]+\z/
name.prepend 'docker-compose ' if subpath =~ /compose\/reference\/./
name.prepend 'docker-machine ' if subpath =~ /machine\/reference\/./
else
name << " (#{product})" if name !~ /#{product}/i
end
name
end
TYPE_BY_SUBPATH = {
'engine/' => 'Engine',
'compose/' => 'Compose',
'machine/' => 'Machine'
}
def get_type
return TYPE_BY_SUBPATH[subpath] if TYPE_BY_SUBPATH[subpath]
return 'Engine: CLI' if subpath.start_with?('engine/reference/commandline/')
return 'Engine: Admin Guide' if subpath.start_with?('engine/admin/')
return 'Engine: Security' if subpath.start_with?('engine/security/')
return 'Engine: Extend' if subpath.start_with?('engine/extend/')
return 'Engine: Get Started' if subpath.start_with?('engine/getstarted')
return 'Engine: Tutorials' if subpath.start_with?('engine/tutorials/')
return product if !nav_link && subpath =~ /\A\w+\/[\w\-]+\/\z/
leaves = nav_link.ancestors('li.leaf').reverse
return product if leaves.length <= 2
type = leaves[0..1].map { |node| node.at_css('> a').content.strip }.join(': ')
type.remove! %r{\ADocker }
type.remove! ' Engine'
type.sub! %r{Command[\-\s]line reference}i, 'CLI'
type.sub! 'CLI reference', 'CLI'
type
end
def nav_link
return @nav_link if defined?(@nav_link)
@nav_link = at_css('.currentPage')
unless @nav_link
link = at_css('#DocumentationText li a')
return unless link
link = at_css(".docsidebarnav_section a[href='#{link['href']}']")
return unless link
@nav_link = link.ancestors('.menu-closed').first.at_css('a')
end
@nav_link
end
def product
@product ||= subpath.split('/').first.capitalize
end
end
end
end

@ -1,56 +0,0 @@
module Docs
class Docker
class EntriesVeryOldFilter < Docs::EntriesFilter
def get_name
name = nav_link ? nav_link.content.strip : at_css('#content h1').content.strip
name.capitalize! if name == 'exoscale'
if name =~ /\A[a-z\-\s]+\z/
name.prepend 'docker ' if subpath =~ /engine\/reference\/commandline\/./
name.prepend 'docker-compose ' if subpath =~ /compose\/reference\/./
name.prepend 'docker-machine ' if subpath =~ /machine\/reference\/./
name.prepend 'swarm ' if subpath =~ /swarm\/reference\/./ && name != 'swarm'
else
name << " (#{product})" if name !~ /#{product}/i
end
name
end
def get_type
unless nav_link
return 'Engine: User guide' if subpath.start_with?('engine/userguide')
end
type = nav_link.ancestors('article').to_a.reverse.to_a[0..1].map do |node|
node.at_css('> button').content.strip
end.join(': ')
type.remove! %r{\ADocker }
type.remove! %r{ Engine}
type.sub! %r{Command[\-\s]line reference}i, 'CLI'
type = 'Engine: Reference' if type == 'Engine: reference'
type
end
def nav_link
return @nav_link if defined?(@nav_link)
@nav_link = at_css('#multiple .active')
unless @nav_link
link = at_css('#content li a')
return unless link
link = at_css("#multiple a[href='#{link['href']}']")
return unless link
@nav_link = link.ancestors('article').first.at_css('button')
end
@nav_link
end
def product
@product ||= subpath.split('/').first.capitalize
end
end
end
end

@ -0,0 +1,25 @@
module Docs
class Eigen3
class CleanHtmlFilter < Filter
def call
@doc = at_css('#doc-content')
css('#MSearchSelectWindow').remove
css('#MSearchResultsWindow').remove
css('.directory .levels').remove
css('.header .summary').remove
css('.ttc').remove
css('.top').remove
css('.dynheader.closed').remove
css('.permalink').remove
css('.groupheader').remove
css('.header').remove
css('#details').remove
css('*').each do |node|
node.remove_attribute('class')
end
doc
end
end
end
end

@ -0,0 +1,123 @@
module Docs
class Eigen3
class EntriesFilter < Docs::EntriesFilter
def get_type
group = at_css('.title .ingroups')
content = at_css('.contents').content
title = get_title()
downtitle = title.downcase
name = get_name
if slug.include?('unsupported')
return 'Unsupported'
elsif slug.start_with?('Topic') || downtitle.end_with?("topics")
return 'Topics'
elsif downtitle.end_with?("class template reference") || downtitle.end_with?("class reference")
return 'Classes'
elsif downtitle.end_with?("struct reference")
return 'Structs'
elsif downtitle.end_with?("typedefs")
return 'Typedefs'
elsif downtitle.end_with?("namespace reference")
return 'Namespaces'
elsif not group.nil? and group.content.include?('Reference') and (downtitle.end_with?("module") || downtitle.end_with?("modules"))
return "Modules"
elsif not group.nil?
if group.children.length > 0
return 'Chapter: ' + group.children[-1].content
else
return 'Chapter: ' + group.content
end
else
return 'Eigen'
end
end
def get_name
title = get_title().gsub(/[<(].*/, '').gsub(/(Class|Class Template|Namespace|Struct) Reference/, '').strip
end
def get_title
unless at_css('.title').nil?
group = at_css('.title .ingroups')
title = at_css('.title').content
if not group.nil?
title = title.delete_suffix(group.content)
end
return title.strip
else
return slug
end
end
def additional_entries
# return [] if slug.include?('unsupported')
name = get_name()
entries = []
css('table.memberdecls').map do |table|
doxygen_type = table.at_css("tr.heading").text.strip
case doxygen_type
when "Functions"
type = "Functions"
when "Public Member Functions", "Static Public Member Functions", "Public Types", "Additional Inherited Members"
type = nil
when "Classes"
type = "Classes"
when "Typedefs"
type = "Typedefs"
when "Variables"
type = "Variables"
else
next
end
tmp_entries = []
table.css('td.memItemRight,td.memTemplItemRight').map do |node_r|
node_l = node_r.parent.at_css('memItemLeft')
if (not node_l.nil? and node_l.text.strip == 'enum') || node_r.content.include?('{')
node_r.css("a").each {|n| tmp_entries << [n.content, n.attr('href')]}
else
n = node_r.at_css("a")
next if n.nil?
tmp_entries << [node_r.content, n.attr('href')]
end
end
tmp_entries.each do |args|
(content, href) = args
next if href.nil?
if not href.include?("#") and (name == 'Eigen' || type == "Classes") then
next
end
if slug.include?('unsupported')
if not (href.include?('unsupported') || href.include?('#'))
next
elsif href.include?('#') and not href.include?('unsupported')
href = 'unsupported/' + href
end
end
if doxygen_type == "Typedefs"
content = content.sub(/\s*=.*$/, "")
end
if not (name.end_with?('module') || name.end_with?('typedefs')) \
and not content.start_with?("Eigen::")
content = name + "::" + content
end
content.gsub! /^\s+/, ''
content.gsub! /\s+,\s+/, ', '
content.gsub! /\s\s+/, ' '
entries << [content, href, type]
end
end
entries
end
end
end
end

@ -2,49 +2,15 @@ module Docs
class Electron
class CleanHtmlFilter < Filter
def call
css('.header-link', 'hr + .text-center', 'hr', '.docs__actions-bar').remove
@doc = at_css(".markdown")
css('.grid', '.row', '.col-ms-12', 'ul.docs-list > ul.docs-list', '.sub-section').each do |node|
node.before(node.children).remove
end
css(".theme-doc-toc-desktop").remove
if root_page?
doc.child.before('<h1>Electron Documentation</h1>')
css(".theme-doc-toc-mobile").remove
css("div.subtron, div.py-6").remove
css(".clean-btn").remove
css('h2 > a').each do |node|
node.before(node.children).remove
end
else
@doc = doc.at_css('div.docs > div.markdown-body')
end
at_css('h2').name = 'h1' if !at_css('h1') && at_css('h2')
css('h3', 'h4', 'h5').each do |node|
node.name = node.name.sub(/\d/) { |i| i.to_i - 1 } unless node.name == 'h3' && node.at_css('code')
end if !at_css('h2') && at_css('h4')
css('h1 > a', 'h2 > a', 'h3 > a', 'h4 > a').each do |node|
node.before(node.children).remove
end
css('div.highlighter-rouge').each do |node|
node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
node.content = node.content.strip
node.name = 'pre'
end
css('pre > code.hljs').each do |node|
node.parent['data-language'] = node['class'][/language-(\w+)/, 1]
end
css('.highlighter-rouge').remove_attr('class')
css('pre').each do |node|
node.content = node.content
end
css("footer").remove
doc
end

@ -3,7 +3,6 @@ module Docs
class EntriesFilter < Docs::EntriesFilter
def get_name
return 'API' if subpath == '/api'
return 'Structures' if slug == 'api/structures'
name = at_css('h1, h2').content
name.remove! 'Class: '
@ -15,7 +14,7 @@ module Docs
end
def get_type
return 'API' if subpath == '/api' || slug == 'api/structures'
return 'API' if subpath == '/api'
if subpath.start_with?('/tutorial') || subpath.in?(%w(/glossary /faq))
'Guides'
@ -34,7 +33,6 @@ module Docs
return [] unless subpath.start_with?('/api')
css('h3 > code', 'h4 > code').each_with_object [] do |node, entries|
next if node.previous.try(:content).present? || node.next.try(:content).present?
name = node.content
name.sub! %r{\(.*\)}, '()'
name.remove! 'new '

@ -3,11 +3,11 @@ module Docs
class EntriesFilter < Docs::EntriesFilter
def get_name
css('h1 .app-vsn').remove
name = (at_css('h1 > span') or at_css('h1')).content.strip
if current_url.path.start_with?('/getting-started')
at_css('h1').content.strip.remove(/\.\z/)
name.remove(/\.\z/)
else
name = at_css('h1').content.strip
name = name.split(' ').first unless name.start_with?('mix ') # ecto
name
end

@ -0,0 +1,19 @@
module Docs
class Esbuild
class CleanHtmlFilter < Filter
def call
css('figure.bench').remove
css('.permalink').remove
css('.switcher').remove
css('pre').each do |node|
node.content = node.content
node['data-language'] = 'javascript'
node['data-language'] = 'sh' if node['class'] && node['class'].include?('cli')
node['data-language'] = 'go' if node['class'] && node['class'].include?('go')
node['class'] = nil
end
doc
end
end
end
end

@ -0,0 +1,21 @@
module Docs
class Esbuild
class EntriesFilter < Docs::EntriesFilter
def name
at_css('h1').content
end
def type
at_css('h1').content
end
def additional_entries
entries = []
type = at_css('h1').content
css('h2[id], h3[id]').each do |node|
entries << [node.content.gsub(/^#/, ''), node['id'], type]
end
entries
end
end
end
end

@ -3,11 +3,6 @@ module Docs
class EntriesFilter < Docs::EntriesFilter
def get_name
name = at_css('h1').content.strip
if subpath.start_with?('rules/') && subpath != 'rules/'
name = name[/\(([\w\-]+?)\)\z/, 1] || name[/\A([\w\-]+?):/, 1]
end
name
end

@ -0,0 +1,54 @@
module Docs
class GnuMake
class CleanHtmlFilter < Filter
def call
if current_url == root_url
# Remove short table contents
css('.shortcontents').remove
css('.shortcontents-heading').remove
css('.contents-heading').remove
css('.contents').remove
css('.settitle').remove
# remove copyright
css('blockquote').remove
end
css('hr').remove
css('.header').remove
# Remove undesirable in headers
css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix').each do |node|
node.content = node.content.slice(/[[:alpha:]]...*/)
node.content = node.content.sub(/Appendix.{2}/, '') if node.content.include?('Appendix')
if node.content.match?(/[[:upper:]]\./)
node.content = node.content.sub(/[[:upper:]]\./, '')
node.content = node.content.gsub(/\./, '')
node.content = node.content.gsub(/[[:digit:]]/, '')
end
node.name = "h1"
end
css('dt code').each do |node|
node.parent['id'] = node.content
end
css('dt > samp').each do |node|
node.parent['id'] = node.content
end
css('br').remove
css('.footnote').remove
doc
end
end
end
end

@ -0,0 +1,97 @@
module Docs
class GnuMake
class EntriesFilter < Docs::EntriesFilter
NO_ADDITIONAL_ENTRIES = [
'Quick Reference', 'Instead of Executing Recipes',
'Loaded Object Interface', 'Conversion of Guile Types',
'Arguments to Specify the Goals', 'Standard Targets for Users',
'Variables for Installation Directories', 'Errors Generated by Make',
'The origin Function', 'The vpath Directive',
'Interfaces from Guile to make', 'Output During Parallel Execution',
'How to Run make', 'The flavor Function', 'Catalogue of Built-In Rules'
]
DL_DT_TABLE = {
'Automatic Variables' => 'Automatic Variables',
'Other Special Variables' => 'Automatic Variables',
'Variables Used by Implicit Rules' => 'Automatic Variables',
'Special Built-in Target Names' => 'Built-in targets',
'Functions for File Names' => 'File Names Functions',
'Functions for String Substitution and Analysis' => 'String Substitution and Analysis Functions',
'Functions for Conditionals' => 'Conditionals Functions',
'Functions That Control Make' => 'Make Control Functions',
'Syntax of Conditionals' => 'Conditionals Syntax'
}
def get_name
name = at_css('.chapter', '.section', '.subsection', '.subsubsection', '.appendix')
if name.nil?
name = at_css('h1, h2, h3').content.slice(/[[:alpha:]]...*/)
else
name = name.content.slice(/[[:alpha:]]...*/)
end
name.gsub!(/Appendix.{2}/, '') if name.include?('Appendix')
# remove withespace at the beginning left when "Appendix" is removed
name.gsub!(/\G\s/, '')
name
end
def get_type
return 'Transforming text functions' if name =~ /The [a-z]+ Function/
return 'Directives' if name =~ /The [a-z]+ Directive/
'Manual'
end
def additional_entries
entries = []
return entries if NO_ADDITIONAL_ENTRIES.include?(name)
css('dl dt').each do |node|
break if name == 'Summary of Options'
entry_type = ""
if DL_DT_TABLE.key?(name)
entry_type = DL_DT_TABLE[name]
else
entry_type = "Entry type missing"
end
entry_name = node.at_css('code')
if entry_name.nil?
next
end
entry_name = entry_name.content
entry_path = slug.downcase + '#' + entry_name
entries << [entry_name, entry_path, entry_type]
end
css('dt > samp').each do |node|
break if name == 'Other Special Variables'
entry_type = 'Automatic Variables' if name == 'Automatic Variables'
entry_type = 'Functions for File Names' if name == 'Functions for File Names'
entry_type = 'Make Cli Options' if name == 'Summary of Options'
entry_name = node.content
entry_path = slug.downcase + '#' + entry_name
entries << [entry_name, entry_path, entry_type]
end
entries
end
end
end
end

@ -0,0 +1,12 @@
# frozen_string_literal: true
module Docs
class Go
class AttributionFilter < Docs::AttributionFilter
def attribution_link
url = current_url.to_s.sub! 'localhost:6060', 'golang.org'
%(<a href="#{url}" class="_attribution-link">#{url}</a>)
end
end
end
end

@ -16,7 +16,8 @@ module Docs
def additional_entries
entries = []
css('.method, .element, .field, .enum_constant').each do |node|
entries << [@name + '.' + node['id'], node['id']]
# Fix useless functions with arg249 https://docs.groovy-lang.org/3.0.9/html/gapi/org/codehaus/groovy/runtime/ArrayUtil.html
entries << [@name + '.' + node['id'], node['id']] if node['id'].length <= 192
end
css('.constructor').each do |node|
entries << [node['id'], node['id']]

@ -1,7 +1,6 @@
module Docs
class Html
class EntriesFilter < Docs::EntriesFilter
OBSOLETE = %w(frame frameset hgroup noframes)
ADDITIONAL_ENTRIES = { 'Element/Heading_Elements' => (1..6).map { |n| ["h#{n}"] } }
def get_name
@ -15,10 +14,10 @@ module Docs
def get_type
return 'Miscellaneous' if slug.include?('CORS') || slug.include?('Using')
if slug.start_with?('Global_attr')
if at_css('.deprecated', '.non-standard', '.obsolete')
'Obsolete'
elsif slug.start_with?('Global_attr')
'Attributes'
elsif at_css('#deprecated', '#non-standard', '#obsolete') || OBSOLETE.include?(slug.remove('Element/'))
'Obsolete'
elsif slug.start_with?('Element/')
'Elements'
else
@ -38,12 +37,14 @@ module Docs
css('.standard-table td:first-child').each_with_object [] do |node, entries|
next if node.next_element.content.include?('Global attribute')
name = "#{node.content.strip} (attribute)"
name = "#{node.at_css('code').content.strip} (attribute)" if node.at_css('code')
id = node.parent['id'] = name.parameterize
entries << [name, id, 'Attributes']
end
elsif slug == 'Link_types'
css('.standard-table td:first-child > code').map do |node|
name = node.content.strip
name = "#{node.at_css('code').content.strip} (attribute)" if node.at_css('code')
id = node.parent.parent['id'] = name.parameterize
name.prepend 'rel: '
[name, id, 'Attributes']

@ -2,7 +2,7 @@ module Docs
class Http
class CleanHtmlFilter < Filter
def call
current_url.host == 'tools.ietf.org' ? ietf : mdn
current_url.host == 'datatracker.ietf.org' ? ietf : mdn
doc
end
@ -43,7 +43,7 @@ module Docs
end
css('.selflink').each do |node|
node.parent['id'] = node['name']
node.parent['id'] = node['id']
node.before(node.children).remove
end

@ -2,7 +2,7 @@ module Docs
class Http
class EntriesFilter < Docs::EntriesFilter
def get_name
if current_url.host == 'tools.ietf.org'
if current_url.host == 'datatracker.ietf.org'
name = at_css('h1').content
name.remove! %r{\A.+\:}
name.remove! %r{\A.+\-\-}
@ -22,7 +22,7 @@ module Docs
end
def get_type
return name if current_url.host == 'tools.ietf.org'
return name if current_url.host == 'datatracker.ietf.org'
if slug.start_with?('Headers/Content-Security-Policy')
'CSP'
@ -101,7 +101,7 @@ module Docs
LEVEL_3 = /\A(\d+)\.\d+\.\d+\z/
def additional_entries
return [] unless current_url.host == 'tools.ietf.org'
return [] unless current_url.host == 'datatracker.ietf.org'
type = nil
css('a[href^="#section-"]').each_with_object([]) do |node, entries|

@ -0,0 +1,17 @@
module Docs
class I3
class EntriesFilter < Docs::EntriesFilter
def additional_entries
entries = []
type = nil
css('h2[id], h3[id]').each do |node|
if node.name == 'h2' && node['id']
type = node.content
end
entries << [node.content, node['id'], type]
end
entries
end
end
end
end

@ -2,6 +2,9 @@ module Docs
class Immutable
class CleanHtmlFilter < Filter
def call
@doc = at_css('div')
css('#algolia-autocomplete', '#algolia-docsearch').remove
css('section', 'span', 'div[data-reactid]').each do |node|
node.before(node.children).remove
end
@ -9,36 +12,11 @@ module Docs
css('.codeBlock').each do |node|
node.name = 'pre'
node.content = node.content
node['data-language'] = 'js'
node['data-language'] = 'ts'
end
css('*[data-reactid]').remove_attr('data-reactid')
css('a[target]').remove_attr('target')
css('a[href^="#"]').each do |node|
node['href'] = node['href'].sub(/\A#\//, '#').gsub('/', '.').downcase
end
type = type_id = nil
css('*').each do |node|
if node.name == 'h1'
node['id'] = type_id = node.content.strip.downcase
type = node.content.strip
elsif node.name == 'h3'
node['id'] = node.content.strip.downcase
node['id'] = node['id'].remove('()') unless node['id'] == "#{type_id}()"
unless node['id'].start_with?(type_id)
node.content = "#{type}##{node.content}"
node['id'] = "#{type_id}.#{node['id']}" unless node['id'].start_with?("#{type_id}.")
end
end
end
css('h4.groupTitle').each do |node|
node.name = 'h2'
end
css('*[class]').remove_attr('class')
doc

@ -1,21 +1,24 @@
module Docs
class Immutable
class EntriesFilter < Docs::EntriesFilter
def get_name
at_css('h1').content
end
def get_type
return 'Util' if slug.match?(/^([a-z]|Range|Repeat)/)
at_css('h1').content
end
def additional_entries
return [] if root_page?
entries = []
type = nil
css('*').each do |node|
if node.name == 'h1'
name = node.content
type = node.content.split('.').first
entries << [name, node['id'], type]
elsif node.name == 'h3'
name = node.content
entries << [name, node['id'], type]
end
css('h2, h3, h4').each do |node|
name = node.content
id = node.parent['id']
next unless id
entries << ["#{type}.#{name}", id, type]
end
entries
end
end

@ -2,21 +2,7 @@ module Docs
class Jasmine
class CleanHtmlFilter < Filter
def call
@doc = at_css('.docs')
at_css('h1').content = 'Jasmine' if root_page?
css('header', 'article', 'section:not([class])', 'div.description').each do |node|
node.before(node.children).remove
end
css('h3.subsection-title').each do |node|
node.name = 'h2'
end
css('h4.name').each do |node|
node.name = 'h3'
end
@doc = at_css('.docs') unless root_page?
css('pre').each do |node|
node.content = node.content

@ -2,24 +2,17 @@ module Docs
class Jasmine
class EntriesFilter < Docs::EntriesFilter
def get_name
name = at_css('h1').content.strip
name.remove! %r{\A\w+:\s}
name
at_css('h1').content.strip
end
def get_type
at_css('h1').content.strip
name
end
def additional_entries
css('h3[id]').each_with_object [] do |node, entries|
name = node.content.strip
next if name.start_with?('new ')
static = name.sub! '(static) ', ''
name.sub! %r{\(.*\)}, '()'
name.remove! %r{\s.*}
name.prepend "#{self.name}#{static ? '.' : '#'}" unless slug == 'global'
entries << [name, node['id']]
css('h4[id]').each_with_object [] do |node, entries|
name = node['id']
entries << [name.sub(/\./, ''), name]
end
end
end

@ -2,36 +2,23 @@ module Docs
class Jest
class CleanHtmlFilter < Filter
def call
@doc = at_css('article')
at_css('.markdown').prepend_child(at_css('h1'))
@doc = at_css('.markdown')
at_css('h1').content = 'Jest Documentation' if root_page?
css('hr', '.hash-link', 'button', '.badge').remove
css('.anchor').each do |node|
node.parent['id'] = node['id']
node.remove
end
css('.prism-code').each do |node|
node.parent.parent.before(node)
node.name = 'pre'
node['data-language'] = 'js'
node['data-language'] = node['class'][/language-(\w+)/, 1] if node['class']
counter = 0
node.css('.token-line').each do |subnode| # add newline each line of the code snippets
if counter == 0
else
subnode.content = "\n#{subnode.content}"
end
counter += 1
end
node.content = node.content
node.remove_attribute('class')
node['data-language'] = 'typescript'
node.content = node.css('.token-line').map(&:content).join("\n")
end
css('*').remove_attribute('style')
doc
end
end

@ -39,7 +39,7 @@ module Docs
name.remove! %r{[\s=<].*}
name.prepend 'jest ' if name.start_with?('--')
name.prepend 'Config: ' if slug == 'configuration'
id = node.at_css('.anchor')['id']
id = node['id']
entries << [name, id]
end

@ -2,7 +2,6 @@ module Docs
class Kotlin
class CleanHtmlFilter < Filter
def call
@doc = at_css('.page-content')
subpath.start_with?('api') ? api_page : doc_page
doc
end
@ -13,17 +12,6 @@ module Docs
css('a > img').each do |node|
node.parent.before(node.parent.content).remove
end
css('pre').each do |node|
node['data-language'] = 'kotlin' if node.at_css('code.language-kotlin')
node['data-language'] = 'groovy' if node.at_css('code.language-groovy')
node['data-language'] = 'javascript' if node.at_css('code.language-javascript')
node['data-language'] = 'xml' if node.at_css('code.language-xml')
node.content = node.content
node.parent.remove_attribute('data-highlight-only')
node.parent.remove_attribute('data-lang')
node.parent.remove_attribute('theme')
end
end
def api_page

@ -6,25 +6,26 @@ module Docs
breadcrumbs[1..-1].join('.')
else
node = (at_css('h1') || at_css('h2'))
return node.content unless node.nil?
subpath[/\/([a-z0-9_-]+)\./][1..-2].titleize.sub('Faq', 'FAQ')
return [breadcrumbs[1..], [node.content]].flatten.join(': ') unless node.nil?
end
end
def get_type
if subpath.start_with?('api')
breadcrumbs[1]
elsif subpath.start_with?('docs/tutorials')
'Tutorials'
elsif subpath.start_with?('docs/reference')
'Reference'
else
breadcrumbs[0]
end
end
private
def breadcrumbs
@breadcrumbs ||= css('.api-docs-breadcrumbs a').map(&:content).map(&:strip)
if subpath.start_with?('api')
@breadcrumbs ||= css('.api-docs-breadcrumbs a').map(&:content).map(&:strip)
else
@breadcrumbs ||= doc.document.at_css('body')['data-breadcrumbs'].split('///')
end
end
end
end

@ -0,0 +1,15 @@
module Docs
class Kubectl
class CleanHtmlFilter < Filter
def call
css('pre').each do |node|
node.content = node.content.squish
node['data-language'] = 'bash'
end
doc
end
end
end
end

@ -0,0 +1,38 @@
module Docs
class Kubectl
class EntriesFilter < Docs::EntriesFilter
def get_name
name
end
def get_type
name
end
def additional_entries
entries = []
group = 'kubectl'
commands = css('h1').to_a()
commands.map do |node|
# handle titles differnetly by converting them into sidebar groups (types)
new_group = at_css("##{node['id']} > strong")
if new_group
group = new_group.content.titleize
else
# prepend kubectl before every command
command_name = 'kubectl ' + node.content
entries << [command_name, node['id'], group]
end
end
entries
end
def include_default_entry?
false
end
end
end
end

@ -0,0 +1,22 @@
module Docs
class Kubernetes
class CleanHtmlFilter < Filter
def call
# remove the API Operations section from the docs
# by removing the h2 of id=Opetations
# and all the preceding elements
css('#Operations ~ *').remove
css('#Operations').remove
# remove horizontal rules
css('hr').remove
# remove footer (1.20)
css('.pre-footer').remove
doc
end
end
end
end

@ -0,0 +1,37 @@
module Docs
class Kubernetes
class EntriesFilter < Docs::EntriesFilter
def get_name
at_css('h1').content
end
def get_type
@doc.parent.css('nav .breadcrumb-item:not(.active)')[-1].content
end
def additional_entries
entries = css('h2').to_a()
# remove the Feedback section
entries.filter! {|node| node.content.strip != 'Feedback' }
# remove the Operations section
entries.filter! {|node| node['id'] != 'Operations' }
# remove the ObjectList section
entries.filter! {|node| node['id'] != name + 'List' }
# remove the Object section, most of the documents start with (h1.Pod => h2.Pod h2.PodSpec ...)
entries.filter! {|node| node['id'] != name }
entries.map do |node|
# split all names into YAML object notation (ConfigMapSpec) ==> (ConfigMap.Spec)
child_name = node.content
if child_name.starts_with?(name) && child_name.length > name.length
child_name = name + child_name.sub(name, '.')
end
[child_name, node['id']]
end
end
end
end
end

@ -2,6 +2,11 @@ module Docs
class Mdn
class CompatTablesFilter < Filter
# Generate browser compatibility table
# Fixes "BCD tables only load in the browser"
# https://github.com/mdn/browser-compat-data
# https://github.com/mdn/yari/tree/main/client/src/document/ingredients/browser-compatibility-table
def call
if at_css('#browser_compatibility') \
and not at_css('#browser_compatibility').next_sibling.classes.include?('warning') \
@ -33,6 +38,26 @@ module Docs
'samsunginternet_android' => 'Samsung Internet'
}
def is_javascript
current_url.to_s.start_with?('https://developer.mozilla.org/en-US/docs/Web/JavaScript')
end
def browsers
if is_javascript
{}.merge(BROWSERS).merge({'deno' => 'Deno', 'nodejs' => 'Node.js'})
else
BROWSERS
end
end
def browser_types
if is_javascript
{'Desktop'=>6, 'Mobile'=>6, 'Server'=>2,}
else
{'Desktop'=>6, 'Mobile'=>6,}
end
end
def generate_compatibility_table()
json_files_uri = request_bcd_uris()
@ -46,7 +71,9 @@ module Docs
end
def request_bcd_uris
index_json = JSON.load(Net::HTTP.get(URI(current_url.to_s + '/index.json')))
url = current_url.to_s + '/index.json'
response = Request.run url
index_json = JSON.load response.body
uris = []
@ -63,9 +90,9 @@ module Docs
return uris
end
def generate_compatibility_table_wrapper(uri)
@json_data = JSON.load(Net::HTTP.get(URI(uri)))['data']
def generate_compatibility_table_wrapper(url)
response = Request.run url
@json_data = JSON.load(response.body)['data']
html_table = generate_basic_html_table()
@ -86,15 +113,15 @@ module Docs
table.css('#bct-browser-type').each do |node|
node.add_child('<th>')
%w(Desktop Mobile).each do |browser_type|
node.add_child("<th colspan=6>#{browser_type}")
browser_types.each do |browser_type, colspan|
node.add_child("<th colspan=#{colspan}>#{browser_type}")
end
end
table.css('#bct-browsers').each do |node|
node.add_child('<th>')
BROWSERS.values.each do |browser|
browsers.values.each do |browser|
node.add_child("<th>#{browser}")
end
end
@ -117,7 +144,7 @@ module Docs
end
BROWSERS.keys.each do |browser_key|
browsers.keys.each do |browser_key|
if key == '__compat'
add_data_to_entry(json['support'][browser_key], last_table_entry)
else
@ -163,15 +190,14 @@ module Docs
version_added.map! do |version|
if version == true
version = 'Yes'
'Yes'
elsif version == false
version = 'No'
'No'
elsif version.is_a?(String)
version
else
version = '?'
'?'
end
version
end
if version_removed[0]
@ -179,6 +205,8 @@ module Docs
else
if version_added[0] == 'No'
format_string = "<td class=bc-supports-no>"
elsif version_added[0] == '?'
format_string = "<td class=bc-supports-unknown>"
else
format_string = "<td class=bc-supports-yes>"
end
@ -201,7 +229,7 @@ module Docs
end
else
format_string = "<td class=bc-supports-no><div>?</div></td>"
format_string = "<td class=bc-supports-unknown><div>?</div></td>"
end
entry.add_child(format_string)

@ -21,6 +21,8 @@ module Docs
css('h1 + ul a').each_with_object [] do |node, entries|
name = node.content.strip
next if name =~ /\A[A-Z]/ || name.start_with?('/')
mod = get_name
name = "#{name} (#{mod})" unless mod.match?(/ngx_http/)
id = node['href'].remove('#')
next if id.blank?

@ -0,0 +1,121 @@
module Docs
class Nix
class CleanHtmlFilter < Filter
def call
if subpath == 'nixpkgs/stable/index.html'
new_root = Nokogiri::XML::Node.new 'div', doc.document
# lib functions
lib_sections = xpath("//*[@id='sec-functions-library']/ancestor::section[1]/section/section")
lib_sections.css('.titlepage .title').each do |title|
title.name = 'h2'
strip_section_number(title.children)
title['id'] = title.content.gsub(/[^a-zA-Z0-9]/, '-')
end
lib_sections.css('.example .title strong').each do |title|
title.content = title.content.sub(/^Example ([0-9.]+)/, 'Example: ')
end
new_root.add_child(lib_sections)
# fetchers
fetcher_sections = xpath("//*[@id='chap-pkgs-fetchers']/ancestor::section[1]/section[position()>1]")
fetcher_sections.css('.titlepage .title').each do |title|
strip_section_number(title.children)
prefix_with(title, 'pkgs') if title.name == 'h2'
end
new_root.add_child(fetcher_sections)
# trivial builders
trivial_sections = xpath("//*[@id='chap-trivial-builders']/ancestor::section[1]/section")
trivial_sections.css('.titlepage .title').each do |title|
strip_section_number(title.children)
prefix_with(title, 'pkgs') if title.name == 'h2'
end
new_root.add_child(trivial_sections)
# special builders
special_sections = xpath("//*[@id='chap-special']/ancestor::section[1]/section")
special_sections.css('.titlepage .title').each do |title|
strip_section_number(title.children)
if title.name == 'h2'
title.children[0].wrap('<code>')
prefix_with(title, 'pkgs')
end
end
new_root.add_child(special_sections)
# image builders
image_sections = xpath("//*[@id='chap-images']/ancestor::section[1]/section")
image_sections.css('.titlepage .title').each do |title|
strip_section_number(title.children)
title.children[0].wrap('<code>')
end
image_sections.each do |section|
prefix = section.at_xpath('*[@class="titlepage"]//*[@class="title"]').content
next unless ["pkgs.dockerTools", "pkgs.ociTools"].include?(prefix)
section.xpath('section/*[@class="titlepage"]//*[@class="title"]').each do |title|
prefix_with(title, prefix)
title['data-add-to-index'] = ''
end
end
new_root.add_child(image_sections)
new_root.css('pre.programlisting').attr('data-language', 'nix')
new_root
elsif subpath == 'nix/stable/expressions/builtins.html'
@doc = doc.at_css('main dl')
# strip out the first entry, `derivation`, the actual documentation
# exists in a separate page
derivation_dt = doc.children.at_css('dt')
if derivation_dt.content.starts_with?('derivation')
derivation_dt.remove
doc.children.at_css('dd').remove
else
raise RuntimeError.new('First entry is not derivation, update the scraper')
end
doc.css('dt').each do |title|
title.name = 'h2'
unwrap(title.at_css('a'))
title.children[0].children.before('builtins.')
end
doc.css('dd').each do |description|
description.name = 'div'
end
doc.css('pre > code').each do |code|
code.parent['data-language'] = 'nix' if code['class'] == 'language-nix'
code.parent['data-language'] = 'xml' if code['class'] == 'language-xml'
unwrap(code)
end
doc
else
doc
end
end
def strip_section_number(title_children)
while title_children.first.content == ''
title_children.shift.remove
end
first_text = title_children.first
return unless first_text.text?
first_text.content = first_text.content.sub(/[0-9.]+ ?/, '')
first_text.remove if first_text.blank?
end
def prefix_with(title_node, text)
title_node.css('code').each do |code|
code.content = "#{text}.#{code.content}" unless code.content.starts_with?("#{text}.")
end
end
def unwrap(node)
node.replace(node.inner_html)
end
end
end
end

@ -0,0 +1,22 @@
module Docs
class Nix
class EntriesFilter < Docs::EntriesFilter
def include_default_entry?
false
end
def additional_entries
css('h2, h3[data-add-to-index]').flat_map do |node|
node.css('code').map do |code|
title = code.content
index = title.rindex('.')
type = title[0...index]
name = title.match(/^[^\s]+/)[0]
[name, node['id'], type]
end
end
end
end
end
end

@ -3,6 +3,10 @@ module Docs
class CleanHtmlFilter < Filter
def call
at_css('#___gatsby').before(at_css('h1'))
css('details').remove
css('.dZYhXG', '.fONtKn').remove
css('.kSYjyK').remove
@ -11,6 +15,11 @@ module Docs
css('.jRndWL').remove_attribute('style')
css('pre').each do |node|
node.content = node.css('.token-line').map(&:content).join("\n")
node['data-language'] = 'javascript'
end
doc
end

@ -2,7 +2,8 @@ module Docs
class Numpy
class CleanHtmlFilter < Filter
def call
at_css('#spc-section-body, main > div')
css('.sphinx-bs.container.pb-4.docutils').remove if root_page?
at_css('main > div > section', '#spc-section-body, main > div')
end
end
end

@ -19,7 +19,7 @@ module Docs
return 'Development'
end
li_a = css('nav li.active > a')
return li_a.last.content if li_a
return li_a.last.content if li_a && li_a.last
end
nav_items = css('.nav.nav-pills.pull-left > li')

@ -13,7 +13,7 @@ module Docs
def additional_entries
css('dl:not([compact]) > dt').each_with_object [] do |node, entries|
name = node.content.gsub(/[A-z0-9\,… ]*\=/, '').strip.split(' ')[0]
name = node.content.gsub(/^: +/, '').gsub(/[A-z0-9\,… ]*\=/, '').strip.split(' ')[0]
entries << [name, node['id'], 'Functions'] unless node['id'] =~ /-\d+\Z/
end
end

@ -7,6 +7,10 @@ module Docs
at_css('h1').content = "OpenJDK #{release} Documentation"
end
css('.header > h1').each do |node|
node.parent.before(node).remove
end
css('.header .sub-title', 'hr', '.table-tabs').remove
# fix ul section that contains summaries or tables
@ -30,7 +34,7 @@ module Docs
end
# add syntax highlight to each method
css('.member-signature').each do |node|
css('.type-signature, .member-signature').each do |node|
node.content = node.content.sub(/\u200B/, '') # fix zero width space characters
node.name = 'pre'
@ -40,8 +44,25 @@ module Docs
node.css('span').each do |subnode|
subnode.name = 'code'
end
end
# convert pseudo tables (made from div) to real tables
css('div.caption').remove
css('.two-column-summary > .col-constructor-name').add_class('col-first')
css('.two-column-summary, .three-column-summary').each do |table|
# table.previous_element.remove if table.previous_element?.classes?.include?('caption')
table.name = 'table'
tr = nil
table.css('div.col-first, div.col-second, div.col-last').each do |td|
if td.classes.include?('col-first')
table.add_child('<tr>')
tr = table.last_element_child
end
td.name = 'td'
td.name = 'th' if td.classes.include?('table-header')
td.remove_attribute('class')
tr.add_child(td.remove)
end
end
doc

@ -23,6 +23,9 @@ module Docs
node['data-language'] = 'python'
end
# table of contents "on this page"
css('.toc-item').remove
# sidebar
css('ul.nav.bd-sidenav').remove
@ -30,8 +33,7 @@ module Docs
css('.headerlink').remove
# next and previous section buttons
css('next-link').remove
css('prev-link').remove
css('.prev-next-area').remove
css('footer').remove

@ -0,0 +1,12 @@
module Docs
class PointCloudLibrary
class CleanHtmlFilter < Filter
def call
@doc = at_css('.contents')
css('.dynheader.closed').remove
css('.permalink').remove
doc
end
end
end
end

Some files were not shown because too many files have changed in this diff Show More

Loading…
Cancel
Save