From 9719f1c0b7efdb6ea784395d2c9b13cdf80095c1 Mon Sep 17 00:00:00 2001 From: Simon Legner Date: Mon, 14 Sep 2026 09:28:22 +0200 Subject: [PATCH 1/4] Add a preference to use the latest version of a documentation Enabling it replaces, at boot, every enabled doc for which a newer version is available with that version, so that e.g. CMake 3.9 becomes CMake 3.12 once it is released. Only numbered versions are migrated; variants that share a name, such as Node.js 10 LTS or the Haxe targets, can't be ordered and are left alone. An empty version means the doc holds the latest version and outranks any number. saveDocs bumps the database schema, which drops the object store of the superseded doc just like disabling it manually does. Closes #744 --- assets/javascripts/app/app.js | 31 ++++ assets/javascripts/app/settings.js | 2 + assets/javascripts/models/doc.js | 49 ++++++ .../templates/pages/settings_tmpl.js | 6 + .../views/content/settings_page.js | 1 + test/assets/doc_version_test.js | 142 ++++++++++++++++++ 6 files changed, 231 insertions(+) create mode 100644 test/assets/doc_version_test.js diff --git a/assets/javascripts/app/app.js b/assets/javascripts/app/app.js index 3ced2c403c..fc063b821d 100644 --- a/assets/javascripts/app/app.js +++ b/assets/javascripts/app/app.js @@ -123,6 +123,7 @@ class App extends Events { (docs.includes(doc.slug) ? this.docs : this.disabledDocs).add(doc); } this.migrateDocs(); + this.migrateToLatestVersions(); this.docs.load(this.start.bind(this), this.onBootError.bind(this), { readCache: true, writeCache: true, @@ -190,6 +191,36 @@ class App extends Events { } } + // With the "latest version" preference enabled, replace the enabled docs for + // which a newer version is available with that version. + migrateToLatestVersions() { + if (!this.settings.get("autoLatestVersion")) { + return; + } + + let needsSaving; + const allDocs = this.docs.all().concat(this.disabledDocs.all()); + + for (var doc of this.docs.all().slice()) { + var latest = doc.findLatestVersion(allDocs); + if (latest === doc) { + continue; + } + this.docs.remove(doc); + this.disabledDocs.add(doc); + if (!this.docs.contains(latest)) { + this.disabledDocs.remove(latest); + this.docs.add(latest); + } + needsSaving = true; + } + + if (needsSaving) { + this.docs.sort(); + this.saveDocs(); + } + } + enableDoc(doc, _onSuccess, onError) { if (this.docs.contains(doc)) { return; diff --git a/assets/javascripts/app/settings.js b/assets/javascripts/app/settings.js index 617830ecc9..113005a65d 100644 --- a/assets/javascripts/app/settings.js +++ b/assets/javascripts/app/settings.js @@ -14,6 +14,7 @@ app.Settings = class Settings { "tips", "noAutofocus", "autoInstall", + "autoLatestVersion", "spaceScroll", "spaceTimeout", "noDocSpecificIcon", @@ -40,6 +41,7 @@ app.Settings = class Settings { spaceScroll: 1, spaceTimeout: 0.5, noDocSpecificIcon: false, + autoLatestVersion: false, }; constructor() { diff --git a/assets/javascripts/models/doc.js b/assets/javascripts/models/doc.js index 0a5c815a85..990c4046df 100644 --- a/assets/javascripts/models/doc.js +++ b/assets/javascripts/models/doc.js @@ -1,6 +1,8 @@ app.models.Doc = class Doc extends app.Model { // Attributes: name, slug, type, version, release, db_size, mtime, links + static NUMBERED_VERSION_RGX = /^\d+(\.\d+)*$/; + constructor() { super(...arguments); this.reset(this); @@ -192,6 +194,53 @@ app.models.Doc = class Doc extends app.Model { ); } + // Whether the doc holds a numbered version of its documentation (e.g. "3.9"), + // as opposed to a variant (e.g. "10 LTS" or "Python"), which can't be + // ordered. An empty version means the doc holds the latest version + // (e.g. `angular`), whereas docs without a version aren't versioned at all. + hasNumberedVersion() { + return ( + this.version === "" || Doc.NUMBERED_VERSION_RGX.test(this.version || "") + ); + } + + // Compares numbered versions (e.g. "3.9" is older than "3.12"). + // An empty version means the latest version and is newer than any other. + isNewerVersionThan(other) { + if (this.version === "" || other.version === "") { + return this.version === "" && other.version !== ""; + } + const version = this.version.split("."); + const otherVersion = other.version.split("."); + for (let i = 0; i < Math.max(version.length, otherVersion.length); i++) { + const diff = + (parseInt(version[i], 10) || 0) - (parseInt(otherVersion[i], 10) || 0); + if (diff !== 0) { + return diff > 0; + } + } + return false; + } + + // Returns the doc holding the latest version of the same documentation among + // `docs`, or the doc itself when there is none. + findLatestVersion(docs) { + let latest = this; + if (!this.hasNumberedVersion()) { + return latest; + } + for (var doc of docs) { + if ( + doc.name === this.name && + doc.hasNumberedVersion() && + doc.isNewerVersionThan(latest) + ) { + latest = doc; + } + } + return latest; + } + isOutdated(status) { if (!status) { return false; diff --git a/assets/javascripts/templates/pages/settings_tmpl.js b/assets/javascripts/templates/pages/settings_tmpl.js index cfd30de1b2..f0f84bfa19 100644 --- a/assets/javascripts/templates/pages/settings_tmpl.js +++ b/assets/javascripts/templates/pages/settings_tmpl.js @@ -60,6 +60,12 @@ app.templates.settingsPage = (settings) => `\ }>Automatically download documentation for offline use Only enable this when bandwidth isn't a concern to you. +