Explorar o código

Merge branch 'main' into add/threejs-docs

Simon Legner hai 9 meses
pai
achega
74aa661e3a
Modificáronse 70 ficheiros con 282 adicións e 119 borrados
  1. 2 2
      .github/workflows/build.yml
  2. 1 1
      .github/workflows/schedule-doc-report.yml
  3. 1 1
      .github/workflows/test.yml
  4. 1 1
      .ruby-version
  5. 1 1
      .tool-versions
  6. 1 1
      COPYRIGHT
  7. 1 1
      Dockerfile
  8. 1 1
      Dockerfile-alpine
  9. 1 1
      Gemfile
  10. 10 6
      Gemfile.lock
  11. 4 4
      README.md
  12. 1 1
      assets/javascripts/lib/license.js
  13. 2 20
      assets/javascripts/lib/util.js
  14. 4 0
      assets/javascripts/news.json
  15. 1 1
      assets/javascripts/templates/pages/about_tmpl.js
  16. 12 20
      assets/javascripts/views/content/entry_page.js
  17. 2 1
      assets/stylesheets/application.css.scss
  18. 3 0
      assets/stylesheets/pages/_mdn.scss
  19. 10 0
      assets/stylesheets/pages/_openlayers.scss
  20. 11 0
      docs/file-scrapers.md
  21. 2 0
      lib/docs/filters/bazel/clean_html.rb
  22. 1 1
      lib/docs/filters/core/normalize_urls.rb
  23. 5 1
      lib/docs/filters/crystal/entries.rb
  24. 3 1
      lib/docs/filters/elixir/clean_html.rb
  25. 1 1
      lib/docs/filters/express/clean_html.rb
  26. 18 0
      lib/docs/filters/openlayers/clean_html.rb
  27. 23 0
      lib/docs/filters/openlayers/entries.rb
  28. 1 1
      lib/docs/filters/phpunit/clean_html.rb
  29. 5 2
      lib/docs/filters/rdoc/container.rb
  30. 1 1
      lib/docs/filters/scala/entries_v2.rb
  31. 2 1
      lib/docs/filters/scikit_image/entries.rb
  32. 1 0
      lib/docs/filters/scikit_learn/clean_html.rb
  33. 3 0
      lib/docs/filters/tensorflow/clean_html.rb
  34. 16 0
      lib/docs/filters/vueuse/entries.rb
  35. 5 0
      lib/docs/scrapers/bazel.rb
  36. 3 2
      lib/docs/scrapers/crystal.rb
  37. 1 1
      lib/docs/scrapers/docker.rb
  38. 12 0
      lib/docs/scrapers/elixir.rb
  39. 1 1
      lib/docs/scrapers/express.rb
  40. 1 1
      lib/docs/scrapers/fastapi.rb
  41. 5 0
      lib/docs/scrapers/flask.rb
  42. 1 1
      lib/docs/scrapers/git.rb
  43. 11 2
      lib/docs/scrapers/go.rb
  44. 1 1
      lib/docs/scrapers/haskell.rb
  45. 1 1
      lib/docs/scrapers/jquery/jquery_ui.rb
  46. 1 1
      lib/docs/scrapers/mdn/dom.rb
  47. 1 1
      lib/docs/scrapers/mdn/javascript.rb
  48. 1 2
      lib/docs/scrapers/meteor.rb
  49. 1 3
      lib/docs/scrapers/nix.rb
  50. 24 0
      lib/docs/scrapers/openlayers.rb
  51. 12 3
      lib/docs/scrapers/phpunit.rb
  52. 1 6
      lib/docs/scrapers/rdoc/minitest.rb
  53. 4 0
      lib/docs/scrapers/rdoc/rails.rb
  54. 5 1
      lib/docs/scrapers/rdoc/ruby.rb
  55. 1 2
      lib/docs/scrapers/redis.rb
  56. 1 1
      lib/docs/scrapers/rust.rb
  57. 4 4
      lib/docs/scrapers/sass.rb
  58. 5 3
      lib/docs/scrapers/scikit_image.rb
  59. 4 4
      lib/docs/scrapers/scikit_learn.rb
  60. 5 0
      lib/docs/scrapers/tensorflow/tensorflow.rb
  61. 5 0
      lib/docs/scrapers/tensorflow/tensorflow_cpp.rb
  62. 2 2
      lib/docs/scrapers/trio.rb
  63. 1 1
      lib/docs/scrapers/vite.rb
  64. 1 1
      lib/docs/scrapers/vueuse.rb
  65. 5 0
      lib/docs/scrapers/werkzeug.rb
  66. 1 1
      lib/tasks/docs.thor
  67. BIN=BIN
      public/icons/docs/openlayers/16.png
  68. BIN=BIN
      public/icons/docs/openlayers/16@2x.png
  69. 2 0
      public/icons/docs/openlayers/SOURCE
  70. 1 1
      test/lib/docs/core/manifest_test.rb

+ 2 - 2
.github/workflows/build.yml

@@ -13,13 +13,13 @@ jobs:
     steps:
     - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
     - name: Set up Ruby
-      uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0
+      uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
       with:
         bundler-cache: true # runs 'bundle install' and caches installed gems automatically
     - name: Run tests
       run: bundle exec rake
     - name: Deploy to Heroku
-      uses: akhileshns/heroku-deploy@581dd286c962b6972d427fcf8980f60755c15520 # v3.13.15
+      uses: akhileshns/heroku-deploy@e3eb99d45a8e2ec5dca08735e089607befa4bf28 # v3.14.15
       with:
         heroku_api_key: ${{secrets.HEROKU_API_KEY}}
         heroku_app_name: "devdocs"

+ 1 - 1
.github/workflows/schedule-doc-report.yml

@@ -11,7 +11,7 @@ jobs:
     steps:
     - uses: actions/checkout@ee0669bd1cc54295c223e0bb666b733df41de1c5 # v2.7.0
     - name: Set up Ruby
-      uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0
+      uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
       with:
         bundler-cache: true # runs 'bundle install' and caches installed gems automatically
     - name: Generate report

+ 1 - 1
.github/workflows/test.yml

@@ -11,7 +11,7 @@ jobs:
     steps:
     - uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4.2.2
     - name: Set up Ruby
-      uses: ruby/setup-ruby@540484a3c0f308b08619664ec40bf6c371d172c3 # v1.205.0
+      uses: ruby/setup-ruby@32110d4e311bd8996b2a82bf2a43b714ccc91777 # v1.221.0
       with:
         bundler-cache: true # runs 'bundle install' and caches installed gems automatically
     - name: Run tests

+ 1 - 1
.ruby-version

@@ -1 +1 @@
-3.3.6
+3.4.2

+ 1 - 1
.tool-versions

@@ -1 +1 @@
-ruby 3.3.6
+ruby 3.4.2

+ 1 - 1
COPYRIGHT

@@ -1,4 +1,4 @@
-Copyright 2013-2024 Thibaut Courouble and other contributors
+Copyright 2013-2025 Thibaut Courouble and other contributors
 
   This Source Code Form is subject to the terms of the Mozilla Public
   License, v. 2.0. If a copy of the MPL was not distributed with this

+ 1 - 1
Dockerfile

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

+ 1 - 1
Dockerfile-alpine

@@ -1,4 +1,4 @@
-FROM ruby:3.3.6-alpine
+FROM ruby:3.4.2-alpine
 
 ENV LANG=C.UTF-8
 ENV ENABLE_SERVICE_WORKER=true

+ 1 - 1
Gemfile

@@ -1,5 +1,5 @@
 source 'https://rubygems.org'
-ruby '3.3.6'
+ruby '3.4.2'
 
 gem 'activesupport', require: false
 gem 'html-pipeline'

+ 10 - 6
Gemfile.lock

@@ -36,7 +36,8 @@ GEM
     exifr (1.4.0)
     ffi (1.15.5)
     fspath (3.1.2)
-    highline (2.0.3)
+    highline (3.1.2)
+      reline
     html-pipeline (2.14.3)
       activesupport (>= 2)
       nokogiri (>= 1.4)
@@ -53,6 +54,7 @@ GEM
       image_optim (~> 0.19)
     image_size (3.3.0)
     in_threads (1.6.0)
+    io-console (0.8.0)
     logger (1.6.2)
     method_source (1.0.0)
     mini_portile2 (2.8.8)
@@ -61,7 +63,7 @@ GEM
     mustermann (3.0.3)
       ruby2_keywords (~> 0.0.1)
     newrelic_rpm (8.16.0)
-    nokogiri (1.17.2)
+    nokogiri (1.18.3)
       mini_portile2 (~> 2.8.2)
       racc (~> 1.4)
     options (2.3.2)
@@ -76,7 +78,7 @@ GEM
       byebug (~> 11.0)
       pry (>= 0.13, < 0.15)
     racc (1.8.1)
-    rack (2.2.10)
+    rack (2.2.11)
     rack-protection (3.2.0)
       base64 (>= 0.1.0)
       rack (~> 2.2, >= 2.2.4)
@@ -88,6 +90,8 @@ GEM
     rb-inotify (0.10.1)
       ffi (~> 1.0)
     redcarpet (3.6.0)
+    reline (0.6.0)
+      io-console (~> 0.5)
     rexml (3.3.9)
     rouge (1.11.1)
     rr (3.1.1)
@@ -126,14 +130,14 @@ GEM
     strings-ansi (0.2.0)
     terminal-table (3.0.2)
       unicode-display_width (>= 1.1.1, < 3)
-    terser (1.2.4)
+    terser (1.2.5)
       execjs (>= 0.3.0, < 3)
     thin (1.8.2)
       daemons (~> 1.0, >= 1.0.9)
       eventmachine (~> 1.0, >= 1.0.4)
       rack (>= 1, < 3)
     thor (1.3.2)
-    tilt (2.4.0)
+    tilt (2.6.0)
     tty-pager (0.14.0)
       strings (~> 0.2.0)
       tty-screen (~> 0.8)
@@ -187,7 +191,7 @@ DEPENDENCIES
   yajl-ruby
 
 RUBY VERSION
-   ruby 3.3.6p108
+   ruby 3.4.2p28
 
 BUNDLED WITH
    2.4.6

+ 4 - 4
README.md

@@ -25,12 +25,12 @@ Unless you wish to contribute to the project, we recommend using the hosted vers
 The easiest way to run DevDocs locally is using Docker:
 
 ```sh
-docker run --name devdocs -d -p 9292:9292 ghcr.io/freecodcamp/devdocs:latest
+docker run --name devdocs -d -p 9292:9292 ghcr.io/freecodecamp/devdocs:latest
 ```
 
 This will start DevDocs at [localhost:9292](http://localhost:9292). We provide both regular and Alpine-based images:
-- `ghcr.io/freecodcamp/devdocs:latest` - Standard image
-- `ghcr.io/freecodcamp/devdocs:latest-alpine` - Alpine-based (smaller size)
+- `ghcr.io/freecodecamp/devdocs:latest` - Standard image
+- `ghcr.io/freecodecamp/devdocs:latest-alpine` - Alpine-based (smaller size)
 
 Images are automatically built and updated monthly with the latest documentation.
 
@@ -195,7 +195,7 @@ Made something cool? Feel free to open a PR to add a new row to this table! You
 
 ## Copyright / License
 
-Copyright 2013–2024 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)
+Copyright 2013–2025 Thibaut Courouble and [other contributors](https://github.com/freeCodeCamp/devdocs/graphs/contributors)
 
 This software is licensed under the terms of the Mozilla Public License v2.0. See the [COPYRIGHT](./COPYRIGHT) and [LICENSE](./LICENSE) files.
 

+ 1 - 1
assets/javascripts/lib/license.js

@@ -1,5 +1,5 @@
 /*
- * Copyright 2013-2024 Thibaut Courouble and other contributors
+ * Copyright 2013-2025 Thibaut Courouble and other contributors
  *
  * This source code is licensed under the terms of the Mozilla
  * Public License, v. 2.0, a copy of which may be obtained at:

+ 2 - 20
assets/javascripts/lib/util.js

@@ -457,13 +457,13 @@ $.noop = function () {};
 
 $.popup = function (value) {
   try {
+    window.open(value.href || value, "_blank", "noopener");
+  } catch (error) {
     const win = window.open();
     if (win.opener) {
       win.opener = null;
     }
     win.location = value.href || value;
-  } catch (error) {
-    window.open(value.href || value, "_blank");
   }
 };
 
@@ -526,21 +526,3 @@ $.highlight = function (el, options) {
   el.classList.add(options.className);
   setTimeout(() => el.classList.remove(options.className), options.delay);
 };
-
-$.copyToClipboard = function (string) {
-  let result;
-  const textarea = document.createElement("textarea");
-  textarea.style.position = "fixed";
-  textarea.style.opacity = 0;
-  textarea.value = string;
-  document.body.appendChild(textarea);
-  try {
-    textarea.select();
-    result = !!document.execCommand("copy");
-  } catch (error) {
-    result = false;
-  } finally {
-    document.body.removeChild(textarea);
-  }
-  return result;
-};

+ 4 - 0
assets/javascripts/news.json

@@ -1,4 +1,8 @@
 [
+  [
+    "2025-02-16",
+    "New documentation: <a href=\"/openlayers/\">OpenLayers</a>"
+  ],
   [
     "2024-11-23",
     "New documentation: <a href=\"/duckdb/\">DuckDB</a>"

+ 1 - 1
assets/javascripts/templates/pages/about_tmpl.js

@@ -32,7 +32,7 @@ app.templates.aboutPage = function () {
 
 <h2 class="_block-heading" id="copyright">Copyright and License</h2>
 <p class="_note">
-  <strong>Copyright 2013&ndash;2024 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br>
+  <strong>Copyright 2013&ndash;2025 Thibaut Courouble and <a href="https://github.com/freeCodeCamp/devdocs/graphs/contributors">other contributors</a></strong><br>
   This software is licensed under the terms of the Mozilla Public License v2.0.<br>
   You may obtain a copy of the source code at <a href="https://github.com/freeCodeCamp/devdocs">github.com/freeCodeCamp/devdocs</a>.<br>
   For more information, see the <a href="https://github.com/freeCodeCamp/devdocs/blob/main/COPYRIGHT">COPYRIGHT</a>

+ 12 - 20
assets/javascripts/views/content/entry_page.js

@@ -96,16 +96,9 @@ app.views.EntryPage = class EntryPage extends app.View {
       return content;
     }
 
-    const links = (() => {
-      const result = [];
-      for (var link in this.entry.doc.links) {
-        var url = this.entry.doc.links[link];
-        result.push(
-          `<a href="${url}" class="_links-link">${EntryPage.LINKS[link]}</a>`,
-        );
-      }
-      return result;
-    })();
+    const links = Object.entries(this.entry.doc.links).map(([link, url]) => {
+      return `<a href="${url}" class="_links-link">${EntryPage.LINKS[link]}</a>`;
+    });
 
     return `<p class="_links">${links.join("")}</p>${content}`;
   }
@@ -203,8 +196,8 @@ app.views.EntryPage = class EntryPage extends app.View {
   }
 
   restore() {
-    let path;
-    if (this.cacheMap[(path = this.entry.filePath())]) {
+    const path = this.entry.filePath();
+    if (this.cacheMap[[path]]) {
       this.render(this.cacheMap[path], true);
       return true;
     }
@@ -217,18 +210,17 @@ app.views.EntryPage = class EntryPage extends app.View {
       this.load();
     } else if (target.classList.contains("_pre-clip")) {
       $.stopEvent(event);
-      target.classList.add(
-        $.copyToClipboard(target.parentNode.textContent)
-          ? "_pre-clip-success"
-          : "_pre-clip-error",
+      navigator.clipboard.writeText(target.parentNode.textContent).then(
+        () => target.classList.add("_pre-clip-success"),
+        () => target.classList.add("_pre-clip-error"),
       );
       setTimeout(() => (target.className = "_pre-clip"), 2000);
     }
   }
 
   onAltC() {
-    let link;
-    if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
+    const link = this.find("._attribution:last-child ._attribution-link");
+    if (!link) {
       return;
     }
     console.log(link.href + location.hash);
@@ -236,8 +228,8 @@ app.views.EntryPage = class EntryPage extends app.View {
   }
 
   onAltO() {
-    let link;
-    if (!(link = this.find("._attribution:last-child ._attribution-link"))) {
+    const link = this.find("._attribution:last-child ._attribution-link");
+    if (!link) {
       return;
     }
     this.delay(() => $.popup(link.href + location.hash));

+ 2 - 1
assets/stylesheets/application.css.scss

@@ -3,7 +3,7 @@
 //= depend_on sprites/docs.json
 
 /*!
- * Copyright 2013-2024 Thibaut Courouble and other contributors
+ * Copyright 2013-2025 Thibaut Courouble and other contributors
  *
  * This source code is licensed under the terms of the Mozilla
  * Public License, v. 2.0, a copy of which may be obtained at:
@@ -97,6 +97,7 @@
         'pages/nushell',
         'pages/octave',
         'pages/openjdk',
+        'pages/openlayers',
         'pages/perl',
         'pages/phalcon',
         'pages/phaser',

+ 3 - 0
assets/stylesheets/pages/_mdn.scss

@@ -28,6 +28,9 @@
 
   p > code, li > code { @extend %label; }
 
+  details { @extend %box; }
+  summary > div { display: inline; }
+
   > .note,
   .notecard, // MDN 2021
   .notice,

+ 10 - 0
assets/stylesheets/pages/_openlayers.scss

@@ -0,0 +1,10 @@
+._openlayers {
+  @extend %simple;
+  .nameContainer {
+    @extend %block-label;
+    > * { display: inline-block; margin: 0; }
+    > .tag-source { float: right; }
+  }
+  .card { @extend %box; margin-bottom: 1rem; padding: 1rem; }
+  .signature, .type-signature { @extend %code; }
+}

+ 11 - 0
docs/file-scrapers.md

@@ -254,6 +254,17 @@ done
 
 ### Nokogiri
 ### Ruby / Minitest
+
+```sh
+git clone https://github.com/seattlerb/minitest
+cd minitest/
+bundle install
+bundle add rdoc hoe
+bundle exec rak docs
+cd ..
+cp -r minitest/docs $DEVDOCS/docs/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`)

+ 2 - 0
lib/docs/filters/bazel/clean_html.rb

@@ -7,8 +7,10 @@ module Docs
         css('devsite-feature-tooltip').remove
         css('devsite-thumb-rating').remove
         css('devsite-toc').remove
+        css('devsite-feedback').remove
         css('a.button-with-icon').remove
         css('button.devsite-heading-link').remove
+        css('.devsite-article-body > span:first-child[style="float: right; line-height: 36px"]').remove
         doc
       end
 

+ 1 - 1
lib/docs/filters/core/normalize_urls.rb

@@ -42,7 +42,7 @@ module Docs
     def fix_url(url)
       if context[:redirections]
         url = URL.parse(url)
-        path = url.path.downcase
+        path = url.path ? url.path.downcase : nil
 
         if context[:redirections].key?(path)
           url.path = context[:redirections][path]

+ 5 - 1
lib/docs/filters/crystal/entries.rb

@@ -16,8 +16,12 @@ 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 = at_css('.type-name').children.reject { |n| n.matches?('.kind') }
+          name.map! { |n| n.text.strip }
+          name.reject! &:empty?
+          name = name.join
           name.remove! %r{\(.*\)}
+          name.strip!
           name
         end
       end

+ 3 - 1
lib/docs/filters/elixir/clean_html.rb

@@ -34,7 +34,9 @@ module Docs
             node.name = 'h3'
             node['id'] = id
 
-            source_href = node.at_css('a.icon-action[title="View Source"]').attr('href')
+            a = node.at_css('a.icon-action[title="View Source"]')
+            a ||= node.at_css('a.icon-action[aria-label="View Source"]')
+            source_href = a.attr('href')
 
             node.content = node.at_css('.signature').inner_text
             node << %(<a href="#{source_href}" class="source">Source</a>)

+ 1 - 1
lib/docs/filters/express/clean_html.rb

@@ -3,7 +3,7 @@ module Docs
     class CleanHtmlFilter < Filter
       def call
         i = 1
-        n = at_css("#navmenu a[href='#{result[:path].split('/').last}']").parent
+        n = at_css("#navmenu .submenu-content a[href='#{result[:path].split('/').last}']").parent
         i += 1 while n && n = n.previous_element
         at_css('h1')['data-level'] = i
 

+ 18 - 0
lib/docs/filters/openlayers/clean_html.rb

@@ -0,0 +1,18 @@
+module Docs
+  class Openlayers
+    class CleanHtmlFilter < Filter
+      def call
+        @doc = at_css('section')
+    
+        at_css('h2').name = 'h1' if at_css('h2')
+
+        css('pre.prettyprint').each do |node|
+          node['data-language'] = node['class'].include?('html') ? 'html' : 'js'
+          node.content = node.content
+        end
+        
+        doc
+      end
+    end
+  end
+end

+ 23 - 0
lib/docs/filters/openlayers/entries.rb

@@ -0,0 +1,23 @@
+module Docs
+  class Openlayers
+    class EntriesFilter < Docs::EntriesFilter
+      def get_name
+        at_css('h2').text.split('~').last.strip
+      end
+
+      def get_type
+        slug[/ol_([^_]+)_/, 1] or 'ol'
+      end
+
+      def additional_entries
+        css('h4.name').each_with_object [] do |node, entries|
+          node['id'] = node.previous_element['id']
+          next if node.at_css('.inherited')
+          name = node.children.find {|n| n.text? }.text.strip
+          name.prepend "#{self.name}."
+          entries << [name, node['id']]
+        end
+      end
+    end
+  end
+end

+ 1 - 1
lib/docs/filters/phpunit/clean_html.rb

@@ -2,7 +2,7 @@ module Docs
   class Phpunit
     class CleanHtmlFilter < Filter
       def call
-        @doc = at_css('.section') if not root_page?
+        @doc = at_css('section') if not root_page?
 
         css('pre').each do |node|
           node['class'] = 'highlight'

+ 5 - 2
lib/docs/filters/rdoc/container.rb

@@ -13,8 +13,11 @@ module Docs
           meta = Nokogiri::XML::Node.new 'dl', doc.document
           meta['class'] = 'meta'
 
-          if parent = at_css('#parent-class-section')
-            meta << %(<dt>Parent:</dt><dd class="meta-parent">#{parent.at_css('.link').inner_html.strip}</dd>)
+          parent = at_css('#parent-class-section')
+          if parent && link = parent.at_css('.link')
+            meta << %(<dt>Parent:</dt><dd class="meta-parent">#{link.inner_html.strip}</dd>)
+          elsif parent && link = parent.at_css('a')
+            meta << %(<dt>Parent:</dt><dd class="meta-parent">#{link.to_html}</dd>)
           end
 
           if includes = at_css('#includes-section')

+ 1 - 1
lib/docs/filters/scala/entries_v2.rb

@@ -71,7 +71,7 @@ module Docs
       # name from the HTML because companion object classes may be broken out into
       # their own entries (by the source documentation). When that happens,
       # we want to group these classes (like `scala.reflect.api.Annotations.Annotation`)
-      # under the package name, and not the fully-qualfied name which would
+      # under the package name, and not the fully-qualified name which would
       # include the companion object.
       def package_name
         name = package_drop_last(slug_parts)

+ 2 - 1
lib/docs/filters/scikit_image/entries.rb

@@ -3,7 +3,8 @@ module Docs
     class EntriesFilter < Docs::EntriesFilter
       def get_name
         name = at_css('h1').content.strip
-        name.remove! "\u{00b6}"
+        name.delete_suffix! "¶"
+        name.delete_suffix! "#"
 
         if slug.start_with?('api')
           name.remove! 'Module: '

+ 1 - 0
lib/docs/filters/scikit_learn/clean_html.rb

@@ -2,6 +2,7 @@ module Docs
   class ScikitLearn
     class CleanHtmlFilter < Filter
       def call
+        @doc = at_css('main article', 'main')
         if root_page?
           css('.row').each do |node|
             html = '<dl>'

+ 3 - 0
lib/docs/filters/tensorflow/clean_html.rb

@@ -46,6 +46,9 @@ module Docs
           node.replace("<p>#{node.to_html}</p>")
         end
 
+        css('span[slot="popout-heading"]').remove
+        css('span[slot="popout-contents"]').remove
+
         doc
       end
     end

+ 16 - 0
lib/docs/filters/vueuse/entries.rb

@@ -248,6 +248,10 @@ module Docs
               "text": "usePreferredReducedMotion",
               "link": "/core/usePreferredReducedMotion/"
             },
+            {
+              "text": "usePreferredReducedTransparency",
+              "link": "/core/usePreferredReducedTransparency/"
+            },
             {
               "text": "useScreenOrientation",
               "link": "/core/useScreenOrientation/"
@@ -264,6 +268,10 @@ module Docs
               "text": "useShare",
               "link": "/core/useShare/"
             },
+            {
+              "text": "useSSRWidth",
+              "link": "/core/useSSRWidth/"
+            },
             {
               "text": "useStyleTag",
               "link": "/core/useStyleTag/"
@@ -313,6 +321,10 @@ module Docs
               "text": "onClickOutside",
               "link": "/core/onClickOutside/"
             },
+            {
+              "text": "onElementRemoval",
+              "link": "/core/onElementRemoval/"
+            },
             {
               "text": "onKeyStroke",
               "link": "/core/onKeyStroke/"
@@ -788,6 +800,10 @@ module Docs
         {
           "text": "Time",
           "items": [
+            {
+              "text": "useCountdown",
+              "link": "/core/useCountdown/"
+            },
             {
               "text": "useDateFormat",
               "link": "/shared/useDateFormat/"

+ 5 - 0
lib/docs/scrapers/bazel.rb

@@ -12,6 +12,11 @@ module Docs
     Licensed under the Creative Commons Attribution 4.0 License, and code samples are licensed under the Apache 2.0 License.
     HTML
 
+    version '8.0' do
+      self.release = '8.0.0'
+      self.base_url = 'https://bazel.build/versions/8.0.0/reference/be/'
+    end
+
     version '7.0' do
       self.release = '7.0.0'
       self.base_url = 'https://bazel.build/versions/7.0.0/reference/be/'

+ 3 - 2
lib/docs/scrapers/crystal.rb

@@ -2,7 +2,7 @@ module Docs
   class Crystal < UrlScraper
     include MultipleBaseUrls
     self.type = 'crystal'
-    self.release = '1.14.0'
+    self.release = '1.15.1'
     self.base_urls = [
       "https://crystal-lang.org/api/#{release}/",
       "https://crystal-lang.org/reference/#{release[0..2]}/",
@@ -21,6 +21,7 @@ module Docs
 
     options[:skip_patterns] = [
       %r{\ACrystal/System/},
+      %r{\ACrystal/PointerPairingHeap/},
       %r{\AIO/Evented.html\z},
       %r{\ARegex/PCRE2.html\z}
     ]
@@ -34,7 +35,7 @@ module Docs
         HTML
       else
         <<-HTML
-          &copy; 2012&ndash;2024 Manas Technology Solutions.<br>
+          &copy; 2012&ndash;2025 Manas Technology Solutions.<br>
           Licensed under the Apache License, Version 2.0.
         HTML
       end

+ 1 - 1
lib/docs/scrapers/docker.rb

@@ -96,7 +96,7 @@ module Docs
 
     def get_latest_version(opts)
       doc = fetch_doc('https://docs.docker.com/engine/release-notes/', opts)
-      latest_version = doc.at_css('.DocSearch-content > h2 > a').content.strip
+      latest_version = doc.at_css('h2.scroll-mt-20 > a').content.strip
       latest_version.rpartition(' ')[-1]
     end
   end

+ 12 - 0
lib/docs/scrapers/elixir.rb

@@ -30,6 +30,18 @@ module Docs
         "https://hexdocs.pm/mix/#{self.class.release}/Mix.html" ]
     end
 
+    version '1.18' do
+      self.release = '1.18.1'
+      self.base_urls = [
+        "https://hexdocs.pm/elixir/#{release}/",
+        "https://hexdocs.pm/eex/#{release}/",
+        "https://hexdocs.pm/ex_unit/#{release}/",
+        "https://hexdocs.pm/iex/#{release}/",
+        "https://hexdocs.pm/logger/#{release}/",
+        "https://hexdocs.pm/mix/#{release}/"
+      ]
+    end
+
     version '1.17' do
       self.release = '1.17.2'
       self.base_urls = [

+ 1 - 1
lib/docs/scrapers/express.rb

@@ -2,7 +2,7 @@ module Docs
   class Express < UrlScraper
     self.name = 'Express'
     self.type = 'express'
-    self.release = '4.18.1'
+    self.release = '4.21.2'
     self.base_url = 'https://expressjs.com/en/'
     self.root_path = '4x/api.html'
     self.initial_paths = %w(

+ 1 - 1
lib/docs/scrapers/fastapi.rb

@@ -2,7 +2,7 @@ module Docs
   class Fastapi < UrlScraper
     self.name = 'FastAPI'
     self.type = 'fastapi'
-    self.release = '0.111.1'
+    self.release = '0.115.6'
     self.base_url = 'https://fastapi.tiangolo.com/'
     self.root_path = '/'
     self.links = {

+ 5 - 0
lib/docs/scrapers/flask.rb

@@ -18,6 +18,11 @@ module Docs
       Licensed under the BSD 3-clause License.
     HTML
 
+    version do
+      self.release = '3.1.1'
+      self.base_url = "https://flask.palletsprojects.com/en/stable/"
+    end
+
     version '3.0' do
       self.release = '3.0.x'
       self.base_url = "https://flask.palletsprojects.com/en/#{self.release}/"

+ 1 - 1
lib/docs/scrapers/git.rb

@@ -1,7 +1,7 @@
 module Docs
   class Git < UrlScraper
     self.type = 'git'
-    self.release = '2.47.1'
+    self.release = '2.48.1'
     self.base_url = 'https://git-scm.com/docs'
     self.initial_paths = %w(/git.html)
     self.links = {

+ 11 - 2
lib/docs/scrapers/go.rb

@@ -1,7 +1,7 @@
 module Docs
   class Go < UrlScraper
     self.type = 'go'
-    self.release = '1.23.0'
+    self.release = '1.24.0'
     self.base_url = 'https://golang.org/pkg/'
     self.links = {
       home: 'https://golang.org/',
@@ -10,10 +10,19 @@ module Docs
 
     # Run godoc locally, since https://golang.org/pkg/ redirects to https://pkg.go.dev/std with rate limiting / scraping protection.
 
-    # podman run --net host --rm -it docker.io/golang:1.23.0
+    # podman run --net host --rm -it docker.io/golang:1.24.0
     #podman# go install golang.org/x/tools/cmd/godoc@latest
     #podman# rm -r /usr/local/go/test/
     #podman# godoc -http 0.0.0.0:6060 -v
+
+    # or using alpine
+    # podman run --net host --rm -it alpine:latest
+    #podman# apk add curl
+    #podman# curl -LO https://go.dev/dl/go1.24.0.linux-amd64.tar.gz
+    #podman# tar xf go1.24.0.linux-amd64.tar.gz
+    #podman# go/bin/go install golang.org/x/tools/cmd/godoc@latest
+    #podman# /root/go/bin/godoc -http 0.0.0.0:6060 -v
+
     self.base_url = 'http://localhost:6060/pkg/'
 
     html_filters.push 'clean_local_urls'

+ 1 - 1
lib/docs/scrapers/haskell.rb

@@ -59,7 +59,7 @@ module Docs
     end
 
     version '9' do
-      self.release = '9.4.2'
+      self.release = '9.12.1'
       self.base_url = "https://downloads.haskell.org/~ghc/#{release}/docs/"
       options[:container] = ->(filter) {filter.subpath.start_with?('users_guide') ? '.document' : '#content'}
     end

+ 1 - 1
lib/docs/scrapers/jquery/jquery_ui.rb

@@ -2,7 +2,7 @@ module Docs
   class JqueryUi < Jquery
     self.name = 'jQuery UI'
     self.slug = 'jqueryui'
-    self.release = '1.13.0'
+    self.release = '1.14.1'
     self.base_url = 'https://api.jqueryui.com'
     self.root_path = '/category/all'
 

+ 1 - 1
lib/docs/scrapers/mdn/dom.rb

@@ -1,6 +1,6 @@
 module Docs
   class Dom < Mdn
-    # release = '2023-08-20'
+    # release = '2025-01-30'
     self.name = 'Web APIs'
     self.slug = 'dom'
     self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/API'

+ 1 - 1
lib/docs/scrapers/mdn/javascript.rb

@@ -3,7 +3,7 @@ module Docs
     prepend FixInternalUrlsBehavior
     prepend FixRedirectionsBehavior
 
-    # release = '2024-11-18'
+    # release = '2025-01-30'
     self.name = 'JavaScript'
     self.base_url = 'https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference'
     self.links = {

+ 1 - 2
lib/docs/scrapers/meteor.rb

@@ -47,8 +47,7 @@ module Docs
     end
 
     def get_latest_version(opts)
-      doc = fetch_doc('https://docs.meteor.com/#/full/', opts)
-      doc.at_css('select.version-select > option').content
+      get_npm_version('meteor', opts)
     end
   end
 end

+ 1 - 3
lib/docs/scrapers/nix.rb

@@ -23,9 +23,7 @@ module Docs
 
     def get_latest_version(opts)
       doc = fetch_doc('https://nixos.org/manual/nix/stable/', opts)
-      json = JSON.parse(doc.at_css('body')['data-nix-channels'])
-      channel = json.find { |c| c['channel'] == 'stable' }
-      channel['version']
+      doc.at_css('a.active')['href'].scan(/([0-9.]+)/)[0][0]
     end
   end
 end

+ 24 - 0
lib/docs/scrapers/openlayers.rb

@@ -0,0 +1,24 @@
+module Docs
+  class Openlayers < UrlScraper
+    self.name = 'OpenLayers'
+    self.type = 'openlayers'
+    self.slug = 'openlayers'
+    self.release = '10.4.0'
+    self.base_url = "https://openlayers.org/en/latest/apidoc/"
+    self.links = {
+      home: 'https://openlayers.org/',
+      code: 'https://github.com/openlayers/openlayers'
+    }
+
+    html_filters.push 'openlayers/entries', 'openlayers/clean_html'
+
+    options[:attribution] = <<-HTML
+      &copy; 2005-present, OpenLayers Contributors All rights reserved.
+      Licensed under the BSD 2-Clause License.
+    HTML
+
+    def get_latest_version(opts)
+      get_npm_version('ol', opts)
+    end
+  end
+end

+ 12 - 3
lib/docs/scrapers/phpunit.rb

@@ -17,12 +17,21 @@ module Docs
     options[:title] = false
 
     options[:attribution] = <<-HTML
-      &copy; 2005&ndash;2020 Sebastian Bergmann<br>
+      &copy; 2005&ndash;2025 Sebastian Bergmann<br>
       Licensed under the Creative Commons Attribution 3.0 Unported License.
     HTML
 
     FILTERS = %w(phpunit/clean_html phpunit/entries title)
 
+    version do
+      self.release = '12.0'
+      self.base_url = "https://docs.phpunit.de/en/#{release}/"
+
+      html_filters.push FILTERS
+
+      options[:container] = '.document'
+    end
+
     version '9' do
       self.release = '9.5'
       self.base_url = "https://phpunit.readthedocs.io/en/#{release}/"
@@ -77,8 +86,8 @@ module Docs
 
     def get_latest_version(opts)
       doc = fetch_doc('https://phpunit.readthedocs.io/', opts)
-      label = doc.at_css('.rst-current-version').content.strip
-      label.scan(/v: ([0-9.]+)/)[0][0]
+      label = doc.at_css('meta[name="readthedocs-version-slug"]')["content"]
+      label
     end
 
   end

+ 1 - 6
lib/docs/scrapers/rdoc/minitest.rb

@@ -1,14 +1,9 @@
 module Docs
   class Minitest < Rdoc
-    # Instructions:
-    #   1. Run "gem update rdoc hoe"
-    #   2. Download the source code at https://github.com/seattlerb/minitest
-    #   3. Run "rake docs" (in the Minitest directory)
-    #   4. Copy the "docs" directory to "docs/minitest"
 
     self.name = 'Ruby / Minitest'
     self.slug = 'minitest'
-    self.release = '5.20.0'
+    self.release = '5.25.4'
     self.links = {
       code: 'https://github.com/minitest/minitest'
     }

+ 4 - 0
lib/docs/scrapers/rdoc/rails.rb

@@ -75,6 +75,10 @@ module Docs
       end
     end
 
+    version '8.0' do
+      self.release = '8.0.1'
+    end
+
     version '7.2' do
       self.release = '7.2.1'
     end

+ 5 - 1
lib/docs/scrapers/rdoc/ruby.rb

@@ -63,12 +63,16 @@ module Docs
       /\AXMP/]
 
     options[:attribution] = <<-HTML
-      Ruby Core &copy; 1993&ndash;2022 Yukihiro Matsumoto<br>
+      Ruby Core &copy; 1993&ndash;2024 Yukihiro Matsumoto<br>
       Licensed under the Ruby License.<br>
       Ruby Standard Library &copy; contributors<br>
       Licensed under their own licenses.
     HTML
 
+    version '3.4' do
+      self.release = '3.4.1'
+    end
+
     version '3.3' do
       self.release = '3.3.0'
     end

+ 1 - 2
lib/docs/scrapers/redis.rb

@@ -22,8 +22,7 @@ module Docs
 
     def get_latest_version(opts)
       body = fetch('http://download.redis.io/redis-stable/00-RELEASENOTES', opts)
-      body = body.lines[1..-1].join
-      body.scan(/Redis ([0-9.]+)/)[0][0]
+      body.scan(/Redis Community Edition ([0-9.]+)/)[0][0]
     end
 
     private

+ 1 - 1
lib/docs/scrapers/rust.rb

@@ -3,7 +3,7 @@
 module Docs
   class Rust < UrlScraper
     self.type = 'rust'
-    self.release = '1.83.0'
+    self.release = '1.84.1'
     self.base_url = 'https://doc.rust-lang.org/'
     self.root_path = 'book/index.html'
     self.initial_paths = %w(

+ 4 - 4
lib/docs/scrapers/sass.rb

@@ -1,7 +1,7 @@
 module Docs
   class Sass < UrlScraper
     self.type = 'yard'
-    self.release = '1.82.9'
+    self.release = '1.85.0'
     self.base_url = 'https://sass-lang.com/documentation'
     self.root_path = 'index.html'
     self.links = {
@@ -17,16 +17,16 @@ module Docs
     options[:trailing_slash] = false
 
     options[:attribution] = <<-HTML
-      &copy; 2006&ndash;2024 the Sass team, and numerous contributors<br>
+      &copy; 2006&ndash;2025 the Sass team, and numerous contributors<br>
       Licensed under the MIT License.
     HTML
-  
-    private
 
     def get_latest_version(opts)
       get_npm_version('sass', opts)
     end
 
+    private
+
     def parse(response)
       response.body.gsub! '<span class="widont">&nbsp;</span>', '&nbsp;'
     end

+ 5 - 3
lib/docs/scrapers/scikit_image.rb

@@ -3,8 +3,10 @@ module Docs
     self.name = 'scikit-image'
     self.slug = 'scikit_image'
     self.type = 'sphinx'
-    self.release = '0.18.1'
-    self.base_url = 'https://scikit-image.org/docs/0.18.x/'
+    self.release = '0.25.0'
+    v = self.release[/\d+\.\d+/]
+    self.base_url = "https://scikit-image.org/docs/#{v}.x/"
+    self.initial_paths = %w(/ /api/ /user_guide/)
     self.links = {
       home: 'https://scikit-image.org/',
       code: 'https://github.com/scikit-image/scikit-image'
@@ -12,7 +14,7 @@ module Docs
 
     html_filters.push 'scikit_image/entries', 'sphinx/clean_html'
 
-    options[:container] = '.span9'
+    options[:container] = 'main article'
     options[:skip] = %w(api_changes.html)
     options[:only_patterns] = [/\Aapi/, /\Auser_guide/]
 

+ 4 - 4
lib/docs/scrapers/scikit_learn.rb

@@ -3,8 +3,9 @@ module Docs
     self.name = 'scikit-learn'
     self.slug = 'scikit_learn'
     self.type = 'sphinx'
-    self.release = '1.1.3'
-    self.base_url = "https://scikit-learn.org/1.1/"
+    self.release = '1.6.1'
+    v = self.release[/\d+\.\d+/]
+    self.base_url = "https://scikit-learn.org/#{v}/"
     self.root_path = 'index.html'
     self.force_gzip = true
     self.links = {
@@ -14,7 +15,6 @@ module Docs
 
     html_filters.push 'scikit_learn/entries', 'scikit_learn/clean_html', 'sphinx/clean_html', 'title'
 
-    options[:container] = ->(filter) { filter.root_page? ? 'body > .container' : '#sk-page-content-wrapper > .body' }
     options[:skip] = %w(modules/generated/sklearn.experimental.enable_iterative_imputer.html
                         modules/generated/sklearn.experimental.enable_hist_gradient_boosting.html)
     options[:only_patterns] = [/\Amodules/, /\Adatasets/, /\Atutorial/, /\Aauto_examples/]
@@ -24,7 +24,7 @@ module Docs
     options[:max_image_size] = 256_000
 
     options[:attribution] = <<-HTML
-      &copy; 2007&ndash;2022 The scikit-learn developers<br>
+      &copy; 2007&ndash;2025 The scikit-learn developers<br>
       Licensed under the 3-clause BSD License.
     HTML
 

+ 5 - 0
lib/docs/scrapers/tensorflow/tensorflow.rb

@@ -19,6 +19,11 @@ module Docs
       Code samples licensed under the Apache 2.0 License.
     HTML
 
+    version do
+      self.release = "2.18.0"
+      self.base_url = "https://www.tensorflow.org/api_docs/python/tf"
+    end
+
     version '2.9' do
       self.release = "2.9.1"
       self.base_url = "https://www.tensorflow.org/versions/r#{version}/api_docs/python/tf"

+ 5 - 0
lib/docs/scrapers/tensorflow/tensorflow_cpp.rb

@@ -3,6 +3,11 @@ module Docs
     self.name = 'TensorFlow C++'
     self.slug = 'tensorflow_cpp'
 
+    version do
+      self.release = "2.18.0"
+      self.base_url = "https://www.tensorflow.org/api_docs/cc"
+    end
+
     version '2.9' do
       self.release = "2.9.1"
       self.base_url = "https://www.tensorflow.org/versions/r#{version}/api_docs/cc"

+ 2 - 2
lib/docs/scrapers/trio.rb

@@ -1,7 +1,7 @@
 module Docs
   class Trio < UrlScraper
     self.type = 'simple'
-    self.release = '0.22.2'
+    self.release = '0.29.0'
     self.base_url = "https://trio.readthedocs.io/en/v#{self.release}/"
     self.root_path = 'index.html'
     self.links = {
@@ -25,7 +25,7 @@ module Docs
 
     def get_latest_version(opts)
       doc = fetch_doc('https://trio.readthedocs.io/en/stable/', opts)
-      doc.at_css('.rst-other-versions a[href^="/en/v"]').content[1..-1]
+      doc.at_css('div.trio-version').content[0..-1]
     end
   end
 end

+ 1 - 1
lib/docs/scrapers/vite.rb

@@ -22,7 +22,7 @@ module Docs
     html_filters.push 'vite/entries', 'vite/clean_html'
 
     version do
-      self.release = '6.0.1'
+      self.release = '6.1.0'
       self.base_url = 'https://vite.dev/'
     end
 

+ 1 - 1
lib/docs/scrapers/vueuse.rb

@@ -22,7 +22,7 @@ module Docs
       Licensed under the MIT License.
     HTML
 
-    self.release = '12.0.0'
+    self.release = '12.5.0'
     self.base_url = 'https://vueuse.org/'
     self.initial_paths = %w(functions.html)
     html_filters.push 'vueuse/entries', 'vite/clean_html', 'vueuse/clean_html'

+ 5 - 0
lib/docs/scrapers/werkzeug.rb

@@ -17,6 +17,11 @@ module Docs
       Licensed under the BSD 3-clause License.
     HTML
 
+    version do
+      self.release = '3.1.1'
+      self.base_url = "https://werkzeug.palletsprojects.com/en/latest/"
+    end
+
     version '3.0' do
       self.release = '3.0.x'
       self.base_url = "https://werkzeug.palletsprojects.com/en/#{self.release}/"

+ 1 - 1
lib/tasks/docs.thor

@@ -239,7 +239,7 @@ class DocsCLI < Thor
           ['index.json', 'meta.json'].each do |filename|
             json = "https://documents.devdocs.io/#{doc.path}/#{filename}?#{time}"
             begin
-              URI.open(json) do |file|
+              URI.open(json, "Accept-Encoding" => "identity") do |file|
                 mutex.synchronize do
                   path = File.join(dir, filename)
                   File.write(path, file.read)

BIN=BIN
public/icons/docs/openlayers/16.png


BIN=BIN
public/icons/docs/openlayers/16@2x.png


+ 2 - 0
public/icons/docs/openlayers/SOURCE

@@ -0,0 +1,2 @@
+https://github.com/openlayers
+https://avatars.githubusercontent.com/u/240579?s=64

+ 1 - 1
test/lib/docs/core/manifest_test.rb

@@ -64,7 +64,7 @@ class ManifestTest < Minitest::Spec
       it "includes the doc's meta representation" do
         json = manifest.as_json
         assert_equal 1, json.length
-        assert_equal "{\"name\"=>\"Test\", \"db_size\"=>776533, :attribution=>\"foo\", :alias=>nil}", json[0].to_s
+        assert_equal "{\"name\" => \"Test\", \"db_size\" => 776533, attribution: \"foo\", alias: nil}", json[0].to_s
       end
     end