1
0
Эх сурвалжийг харах

feat: new version comparison library

Sv443 11 сар өмнө
parent
commit
145fae680c

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

@@ -12,7 +12,7 @@ jobs:
     name: Build
     runs-on: ubuntu-latest
 
-    timeout-minutes: 5
+    timeout-minutes: 8
 
     strategy:
       matrix:

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

@@ -10,7 +10,7 @@ jobs:
     name: Lint
     runs-on: ubuntu-latest
 
-    timeout-minutes: 5
+    timeout-minutes: 8
 
     strategy:
       matrix:

+ 5 - 0
assets/require.json

@@ -13,5 +13,10 @@
     "pkgName": "marked",
     "path": "lib/marked.umd.js",
     "global": "marked"
+  },
+  {
+    "pkgName": "compare-versions",
+    "path": "lib/umd/index.js",
+    "global": "compareVersions"
   }
 ]

+ 72 - 10
assets/translations/README.md

@@ -16,15 +16,15 @@ To submit or edit a translation, please follow [this guide](../../contributing.m
 ### Translation progress:
 |   | Locale | Translated keys | Based on |
 | :----: | ------ | --------------- | :------: |
-| ─ | [`en_US`](./en_US.json) | 214 (default locale) |  |
-| ✅ | [`de_DE`](./de_DE.json) | `214/214` (100%) | ─ |
-| ─ | [`en_UK`](./en_UK.json) | `214/214` (100%) | `en_US` |
-| ✅ | [`es_ES`](./es_ES.json) | `214/214` (100%) | ─ |
-| ✅ | [`fr_FR`](./fr_FR.json) | `214/214` (100%) | ─ |
-| ✅ | [`hi_IN`](./hi_IN.json) | `214/214` (100%) | ─ |
-| ✅ | [`ja_JA`](./ja_JA.json) | `214/214` (100%) | ─ |
-| ✅ | [`pt_BR`](./pt_BR.json) | `214/214` (100%) | ─ |
-| ✅ | [`zh_CN`](./zh_CN.json) | `214/214` (100%) | ─ |
+| ─ | [`en_US`](./en_US.json) | 216 (default locale) |  |
+| ⚠ | [`de_DE`](./de_DE.json) | `214/216` (99.1%) | ─ |
+| ─ | [`en_UK`](./en_UK.json) | `216/216` (100%) | `en_US` |
+| ⚠ | [`es_ES`](./es_ES.json) | `214/216` (99.1%) | ─ |
+| ⚠ | [`fr_FR`](./fr_FR.json) | `214/216` (99.1%) | ─ |
+| ⚠ | [`hi_IN`](./hi_IN.json) | `214/216` (99.1%) | ─ |
+| ⚠ | [`ja_JA`](./ja_JA.json) | `214/216` (99.1%) | ─ |
+| ⚠ | [`pt_BR`](./pt_BR.json) | `214/216` (99.1%) | ─ |
+| ⚠ | [`zh_CN`](./zh_CN.json) | `214/216` (99.1%) | ─ |
 
 <sub>
 ✅ - Fully translated
@@ -44,4 +44,66 @@ This means to figure out which keys are untranslated, you will need to manually
 <br>
 
 ### Missing keys:
-No missing keys
+
+<details><summary><code>de_DE</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>es_ES</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>fr_FR</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>hi_IN</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>ja_JA</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>pt_BR</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>
+
+<details><summary><code>zh_CN</code> - 2 missing keys <i>(click to show)</i></summary><br>
+
+| Key | English text |
+| --- | ------------ |
+| `plugin_validation_error_invalid_property-1` | `Property '%1' with value '%2' is invalid. Example value: %3` |
+| `plugin_validation_error_invalid_property-n` | `Property '%1' with value '%2' is invalid. Example values: %3` |
+
+<br></details>

+ 3 - 1
assets/translations/en_US.json

@@ -231,6 +231,8 @@
     "feature_desc_advancedMode": "Show advanced settings (reloads the menu)",
     "feature_helptext_advancedMode": "After enabling this, the menu will reload and show advanced settings that are hidden by default.\nThis is useful if you want to more deeply customize the script's behavior and don't care about an overcrowded menu.",
 
-    "plugin_validation_error_no_property": "No property '%1' with type '%2'"
+    "plugin_validation_error_no_property": "No property '%1' with type '%2'",
+    "plugin_validation_error_invalid_property-1": "Property '%1' with value '%2' is invalid. Example value: %3",
+    "plugin_validation_error_invalid_property-n": "Property '%1' with value '%2' is invalid. Example values: %3"
   }
 }

+ 11 - 0
changelog.md

@@ -1,6 +1,17 @@
 <!-- I messed up with the changelog parsing so this is just how it will have to be -->
 <div class="split"></div>
 
+## 2.1.0
+- **Internal Changes:**
+  - Removed `compareVersions()` and `compareVersionArrays()` in favor of including the [`compare-versions`](https://npmjs.com/package/compare-versions) library
+
+<!-- <div class="pr-link-cont">
+  <a href="https://github.com/Sv443/BetterYTM/pull/TODO" rel="noopener noreferrer">See pull request for more info</a>
+</div> -->
+
+<div class="split"></div>
+<br>
+
 ## 2.0.0
 - **Added features:**
 	- Keep the volume synced between tabs

+ 20 - 58
contributing.md

@@ -145,6 +145,20 @@ Note: the tab needs to stay open on Firefox or the script will not update itself
 BetterYTM has a built-in interface based on events and exposed global constants and functions that allows other userscripts to benefit from its features.  
 If you want your plugin to be displayed in the readme and possibly inside the userscript itself, please [submit an issue using the plugin submission template](https://github.com/Sv443/BetterYTM/issues/new/choose)  
   
+<br>
+
+**Strongly recommended knowledge:**
+- Intermediate JavaScript knowledge (DOM, events, async functions, fetch, localStorage, etc.)
+  - Understanding JS types and reading TypeScript definitions
+  - Semantic versioning (for versioning your plugin in the correct format)
+- Basic knowledge of userscripts (start on the [GreaseSpot wiki](https://wiki.greasespot.net/Greasemonkey_Manual))
+  
+**Helpful knowledge:**
+- TypeScript (for type safety and better autocomplete)
+- This document, as it contains most of the information you need to know about the BetterYTM interface, or at least points you to the places where you can find it
+  
+<br>
+
 These are the ways to interact with BetterYTM; constants, events and global functions:  
 - Static interaction is done through constants that are exposed through the global `BYTM` object, which is available on the `window` object.  
   These read-only properties tell you more about how BetterYTM is currently being run.  
@@ -161,7 +175,12 @@ These are the ways to interact with BetterYTM; constants, events and global func
 - Another way of dynamically interacting is through global functions, which are also exposed by BetterYTM through the global `BYTM` object.  
   You can find all functions that are available in the `InterfaceFunctions` type in [`src/types.ts`](src/types.ts)  
   There is also a summary with examples [below.](#global-functions)  
-  Additionally to those functions, the namespace `BYTM.UserUtils` is also exposed, which contains all exported members from the [UserUtils library.](https://github.com/Sv443-Network/UserUtils)
+
+- Additionally, the following namespaces expose entire libraries for you that BetterYTM has already loaded in:
+  - `unsafeWindow.BYTM.UserUtils` contains all exported members from the [UserUtils library.](https://github.com/Sv443-Network/UserUtils)  
+    This library can register listeners for when CSS selectors exist, intercept events, manage persistent user configurations, allow you to modify the DOM more easily and more.
+  - `unsafeWindow.BYTM.compareVersions` has all functions from the [compare-versions library.](https://npmjs.com/package/compare-versions)  
+    Use it to compare semver-compliant version strings.
 
 All of these interactions require the use of `unsafeWindow`, as the regular window object is pretty sandboxed in userscript managers.  
   
@@ -297,8 +316,6 @@ The usage and example blocks on each are written in TypeScript but can be used i
   - [sanitizeSong()](#sanitizesong) - Sanitizes the specified song title string to be used in fetching a lyrics URL
 - Other:
   - [NanoEmitter](#nanoemitter) - Abstract class for creating lightweight, type safe event emitting classes
-  - [compareVersions()](#compareversions) - Crudely compares two semver version strings and returns which one is newer
-  - [compareVersionArrays()](#compareversionarrays) - Crudely compares two semver version number arrays and returns which one is newer
 
 <br><br>
 
@@ -1271,59 +1288,4 @@ The usage and example blocks on each are written in TypeScript but can be used i
 > ```
 > </details>
 
-<br>
-
-> ### compareVersions()
-> Usage:
-> ```ts
-> unsafeWindow.BYTM.compareVersions(verA: string, verB: string): -1 | 0 | 1
-> ```
->   
-> Description:
-> Crudely compares two semver version strings.  
-> The format is assumed to *always* be `MAJOR.MINOR.PATCH`, where each part is a positive integer number without leading zeroes.  
-> The function returns:
-> - `-1`, when `verA < verB`
-> - `0`, when `verA == verB`
-> - `1`, when `verA > verB`
-> 
-> <details><summary><b>Example <i>(click to expand)</i></b></summary>
-> 
-> ```ts
-> unsafeWindow.BYTM.compareVersions("1.2.3", "1.2.4");   // -1
-> unsafeWindow.BYTM.compareVersions("1.2.3", "1.2.3");   // 0
-> unsafeWindow.BYTM.compareVersions("1.2.3", "1.2.2");   // 1
-> unsafeWindow.BYTM.compareVersions("1.2.3", "invalid"); // throws a TypeError
-> ```
-> </details>
-
-<br>
-
-> ### compareVersionArrays()
-> Usage:
-> ```ts
-> unsafeWindow.BYTM.compareVersionArrays(verA: [number, number, number], verB: [number, number, number]): -1 | 0 | 1
-> ```
->   
-> Description:  
-> Crudely compares two semver version arrays.  
-> The format is assumed to *always* be `[MAJOR, MINOR, PATCH]`, where each part is a positive integer number.  
-> The function returns:
-> - `-1`, when `verA < verB`
-> - `0`, when `verA == verB`
-> - `1`, when `verA > verB`
->   
-> <details><summary><b>Example <i>(click to expand)</i></b></summary>
-> 
-> ```ts
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [1, 2, 4]);   // -1
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [1, 2, 3]);   // 0
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [1, 2, 2]);   // 1
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [1, 2]);      // throws a TypeError
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [-1, 2, 3]);  // throws a TypeError
-> unsafeWindow.BYTM.compareVersionArrays([1, 2, 3], [1.1, 2, 3]); // throws a TypeError
-> ```
-> 
-> </details>
-
 <br><br><br><br><br><br>

+ 7 - 169
package-lock.json

@@ -6,10 +6,11 @@
   "packages": {
     "": {
       "name": "betterytm",
-      "version": "1.1.1",
+      "version": "2.0.0",
       "license": "AGPL-3.0-only",
       "dependencies": {
         "@sv443-network/userutils": "^6.3.0",
+        "compare-versions": "^6.1.0",
         "fuse.js": "^7.0.0",
         "marked": "^12.0.0",
         "nanoevents": "^9.0.0"
@@ -32,7 +33,6 @@
         "nodemon": "^3.0.1",
         "rollup": "^4.6.0",
         "rollup-plugin-execute": "^1.1.1",
-        "rollup-plugin-html": "^0.2.1",
         "rollup-plugin-import-css": "^3.3.5",
         "ts-node": "^10.9.1",
         "tslib": "^2.5.2",
@@ -1331,16 +1331,6 @@
         "node": ">=6"
       }
     },
-    "node_modules/camel-case": {
-      "version": "3.0.0",
-      "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-3.0.0.tgz",
-      "integrity": "sha512-+MbKztAYHXPr1jNTSKQF52VpcFjwY5RkR7fxksV8Doo4KAYc5Fl4UJRgthBbTmEx8C54DqahhbLJkDwjI3PI/w==",
-      "dev": true,
-      "dependencies": {
-        "no-case": "^2.2.0",
-        "upper-case": "^1.1.1"
-      }
-    },
     "node_modules/chalk": {
       "version": "4.1.2",
       "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
@@ -1405,18 +1395,6 @@
         "node": ">= 6"
       }
     },
-    "node_modules/clean-css": {
-      "version": "4.2.4",
-      "resolved": "https://registry.npmjs.org/clean-css/-/clean-css-4.2.4.tgz",
-      "integrity": "sha512-EJUDT7nDVFDvaQgAo2G/PJvxmp1o/c6iXLbswsBbUFXi1Nr+AjA2cKmfbKDMjMvzEe75g3P6JkaDDAKk96A85A==",
-      "dev": true,
-      "dependencies": {
-        "source-map": "~0.6.0"
-      },
-      "engines": {
-        "node": ">= 4.0"
-      }
-    },
     "node_modules/clean-stack": {
       "version": "2.2.0",
       "resolved": "https://registry.npmjs.org/clean-stack/-/clean-stack-2.2.0.tgz",
@@ -1477,6 +1455,11 @@
         "node": ">= 6"
       }
     },
+    "node_modules/compare-versions": {
+      "version": "6.1.0",
+      "resolved": "https://registry.npmjs.org/compare-versions/-/compare-versions-6.1.0.tgz",
+      "integrity": "sha512-LNZQXhqUvqUTotpZ00qLSaify3b4VFD588aRr8MKFw4CMUr98ytzCW5wDH5qx/DEY5kCDXcbcRuCqL0szEf2tg=="
+    },
     "node_modules/compose-function": {
       "version": "3.0.3",
       "resolved": "https://registry.npmjs.org/compose-function/-/compose-function-3.0.3.tgz",
@@ -2456,42 +2439,6 @@
         "node": ">= 0.4"
       }
     },
-    "node_modules/he": {
-      "version": "1.2.0",
-      "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
-      "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
-      "dev": true,
-      "bin": {
-        "he": "bin/he"
-      }
-    },
-    "node_modules/html-minifier": {
-      "version": "3.5.21",
-      "resolved": "https://registry.npmjs.org/html-minifier/-/html-minifier-3.5.21.tgz",
-      "integrity": "sha512-LKUKwuJDhxNa3uf/LPR/KVjm/l3rBqtYeCOAekvG8F1vItxMUpueGd94i/asDDr8/1u7InxzFA5EeGjhhG5mMA==",
-      "dev": true,
-      "dependencies": {
-        "camel-case": "3.0.x",
-        "clean-css": "4.2.x",
-        "commander": "2.17.x",
-        "he": "1.2.x",
-        "param-case": "2.1.x",
-        "relateurl": "0.2.x",
-        "uglify-js": "3.4.x"
-      },
-      "bin": {
-        "html-minifier": "cli.js"
-      },
-      "engines": {
-        "node": ">=4"
-      }
-    },
-    "node_modules/html-minifier/node_modules/commander": {
-      "version": "2.17.1",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.17.1.tgz",
-      "integrity": "sha512-wPMUt6FnH2yzG95SA6mzjQOEKUU3aLaDEmzs1ti+1E9h+CsrZghRlqEM/EJ4KscsQVG8uNN4uVreUeT8+drlgg==",
-      "dev": true
-    },
     "node_modules/http-errors": {
       "version": "2.0.0",
       "resolved": "https://registry.npmjs.org/http-errors/-/http-errors-2.0.0.tgz",
@@ -2942,12 +2889,6 @@
       "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==",
       "dev": true
     },
-    "node_modules/lower-case": {
-      "version": "1.1.4",
-      "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-1.1.4.tgz",
-      "integrity": "sha512-2Fgx1Ycm599x+WGpIYwJOvsjmXFzTSc34IwDWALRA/8AopUKAVPwfJ+h5+f85BCp0PWmmJcWzEpxOpoXycMpdA==",
-      "dev": true
-    },
     "node_modules/magic-string": {
       "version": "0.16.0",
       "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.16.0.tgz",
@@ -3115,15 +3056,6 @@
         "node": ">= 0.6"
       }
     },
-    "node_modules/no-case": {
-      "version": "2.3.2",
-      "resolved": "https://registry.npmjs.org/no-case/-/no-case-2.3.2.tgz",
-      "integrity": "sha512-rmTZ9kz+f3rCvK2TD1Ue/oZlns7OGoIWP4fc3llxxRXlOkHKoWPPWJOfFYpITabSow43QJbRIoHQXtt10VldyQ==",
-      "dev": true,
-      "dependencies": {
-        "lower-case": "^1.1.1"
-      }
-    },
     "node_modules/nodemon": {
       "version": "3.1.0",
       "resolved": "https://registry.npmjs.org/nodemon/-/nodemon-3.1.0.tgz",
@@ -3326,15 +3258,6 @@
         "url": "https://github.com/sponsors/sindresorhus"
       }
     },
-    "node_modules/param-case": {
-      "version": "2.1.1",
-      "resolved": "https://registry.npmjs.org/param-case/-/param-case-2.1.1.tgz",
-      "integrity": "sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==",
-      "dev": true,
-      "dependencies": {
-        "no-case": "^2.2.0"
-      }
-    },
     "node_modules/parent-module": {
       "version": "1.0.1",
       "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
@@ -3572,15 +3495,6 @@
       "integrity": "sha512-dYnhHh0nJoMfnkZs6GmmhFknAGRrLznOu5nc9ML+EJxGvrx6H7teuevqVqCuPcPK//3eDrrjQhehXVx9cnkGdw==",
       "dev": true
     },
-    "node_modules/relateurl": {
-      "version": "0.2.7",
-      "resolved": "https://registry.npmjs.org/relateurl/-/relateurl-0.2.7.tgz",
-      "integrity": "sha512-G08Dxvm4iDN3MLM0EsP62EDV9IuhXPR6blNz6Utcp7zyV3tr4HVNINt6MpaRWbxoOHT3Q7YN2P+jaHX8vUbgog==",
-      "dev": true,
-      "engines": {
-        "node": ">= 0.10"
-      }
-    },
     "node_modules/require-directory": {
       "version": "2.1.1",
       "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
@@ -3688,16 +3602,6 @@
       "integrity": "sha512-isCNR/VrwlEfWJMwsnmt5TBRod8dW1IjVRxcXCBrxDmVTeA1IXjzeLSS3inFBmRD7KDPlo38KSb2mh5v5BoWgA==",
       "dev": true
     },
-    "node_modules/rollup-plugin-html": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/rollup-plugin-html/-/rollup-plugin-html-0.2.1.tgz",
-      "integrity": "sha512-qnyToGUAjjG69+M+KitUsHnfnLjpjtZdO3nIP0LN50KG/r6zEoBq/pfneAwWkxY/z13zM5aFFXSBY6+6M7bvUw==",
-      "dev": true,
-      "dependencies": {
-        "html-minifier": "^3.0.2",
-        "rollup-pluginutils": "^1.5.0"
-      }
-    },
     "node_modules/rollup-plugin-import-css": {
       "version": "3.5.0",
       "resolved": "https://registry.npmjs.org/rollup-plugin-import-css/-/rollup-plugin-import-css-3.5.0.tgz",
@@ -3713,44 +3617,6 @@
         "rollup": "^2.x.x || ^3.x.x || ^4.x.x"
       }
     },
-    "node_modules/rollup-pluginutils": {
-      "version": "1.5.2",
-      "resolved": "https://registry.npmjs.org/rollup-pluginutils/-/rollup-pluginutils-1.5.2.tgz",
-      "integrity": "sha512-SjdWWWO/CUoMpDy8RUbZ/pSpG68YHmhk5ROKNIoi2En9bJ8bTt3IhYi254RWiTclQmL7Awmrq+rZFOhZkJAHmQ==",
-      "dev": true,
-      "dependencies": {
-        "estree-walker": "^0.2.1",
-        "minimatch": "^3.0.2"
-      }
-    },
-    "node_modules/rollup-pluginutils/node_modules/brace-expansion": {
-      "version": "1.1.11",
-      "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
-      "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
-      "dev": true,
-      "dependencies": {
-        "balanced-match": "^1.0.0",
-        "concat-map": "0.0.1"
-      }
-    },
-    "node_modules/rollup-pluginutils/node_modules/estree-walker": {
-      "version": "0.2.1",
-      "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-0.2.1.tgz",
-      "integrity": "sha512-6/I1dwNKk0N9iGOU3ydzAAurz4NPo/ttxZNCqgIVbWFvWyzWBSNonRrJ5CpjDuyBfmM7ENN7WCzUi9aT/UPXXQ==",
-      "dev": true
-    },
-    "node_modules/rollup-pluginutils/node_modules/minimatch": {
-      "version": "3.1.2",
-      "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
-      "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
-      "dev": true,
-      "dependencies": {
-        "brace-expansion": "^1.1.7"
-      },
-      "engines": {
-        "node": "*"
-      }
-    },
     "node_modules/run-parallel": {
       "version": "1.2.0",
       "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
@@ -4331,28 +4197,6 @@
         "node": ">=14.17"
       }
     },
-    "node_modules/uglify-js": {
-      "version": "3.4.10",
-      "resolved": "https://registry.npmjs.org/uglify-js/-/uglify-js-3.4.10.tgz",
-      "integrity": "sha512-Y2VsbPVs0FIshJztycsO2SfPk7/KAF/T72qzv9u5EpQ4kB2hQoHlhNQTsNyy6ul7lQtqJN/AoWeS23OzEiEFxw==",
-      "dev": true,
-      "dependencies": {
-        "commander": "~2.19.0",
-        "source-map": "~0.6.1"
-      },
-      "bin": {
-        "uglifyjs": "bin/uglifyjs"
-      },
-      "engines": {
-        "node": ">=0.8.0"
-      }
-    },
-    "node_modules/uglify-js/node_modules/commander": {
-      "version": "2.19.0",
-      "resolved": "https://registry.npmjs.org/commander/-/commander-2.19.0.tgz",
-      "integrity": "sha512-6tvAOO+D6OENvRAh524Dh9jcfKTYDQAqvqezbCW82xj5X0pSrcpxtvRKHLG0yBY6SD7PSDrJaj+0AiOcKVd1Xg==",
-      "dev": true
-    },
     "node_modules/undefsafe": {
       "version": "2.0.5",
       "resolved": "https://registry.npmjs.org/undefsafe/-/undefsafe-2.0.5.tgz",
@@ -4383,12 +4227,6 @@
         "node": ">= 0.8"
       }
     },
-    "node_modules/upper-case": {
-      "version": "1.1.3",
-      "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-1.1.3.tgz",
-      "integrity": "sha512-WRbjgmYzgXkCV7zNVpy5YgrHgbBv126rMALQQMrmzOVC4GM2waQ9x7xtm8VU+1yF2kWyPzI9zbZ48n4vSxwfSA==",
-      "dev": true
-    },
     "node_modules/uri-js": {
       "version": "4.4.1",
       "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",

+ 1 - 1
package.json

@@ -60,6 +60,7 @@
   },
   "dependencies": {
     "@sv443-network/userutils": "^6.3.0",
+    "compare-versions": "^6.1.0",
     "fuse.js": "^7.0.0",
     "marked": "^12.0.0",
     "nanoevents": "^9.0.0"
@@ -82,7 +83,6 @@
     "nodemon": "^3.0.1",
     "rollup": "^4.6.0",
     "rollup-plugin-execute": "^1.1.1",
-    "rollup-plugin-html": "^0.2.1",
     "rollup-plugin-import-css": "^3.3.5",
     "ts-node": "^10.9.1",
     "tslib": "^2.5.2",

+ 0 - 2
rollup.config.mjs

@@ -1,7 +1,6 @@
 import pluginTypeScript from "@rollup/plugin-typescript";
 import pluginNodeResolve from "@rollup/plugin-node-resolve";
 import pluginJson from "@rollup/plugin-json";
-import pluginHtml from "rollup-plugin-html";
 import pluginCss from "rollup-plugin-import-css";
 import pluginExecute from "rollup-plugin-execute";
 import typescript from "typescript";
@@ -49,7 +48,6 @@ export default (/**@type {import("./src/types").RollupArgs}*/ args) => (async ()
         sourceMap: mode === "development",
       }),
       pluginJson(),
-      pluginHtml(),
       pluginCss({
         output: "BetterYTM.css",
       }),

+ 4 - 50
src/features/versionCheck.ts

@@ -2,6 +2,8 @@ import { scriptInfo } from "../constants";
 import { getFeatures } from "../config";
 import { error, info, sendRequest, t } from "../utils";
 import { getVersionNotifDialog } from "../dialogs";
+import { compare } from "compare-versions";
+import { LogLevel } from "../types";
 
 const releaseURL = "https://github.com/Sv443/BetterYTM/releases/latest";
 
@@ -42,60 +44,12 @@ export async function doVersionCheck(notifyNoUpdatesFound = false) {
   if(!latestTag)
     return noUpdateFound();
 
-  const versionComp = compareVersions(scriptInfo.version, latestTag);
+  info("Version check - current version:", scriptInfo.version, "- latest version:", latestTag, LogLevel.Info);
 
-  info("Version check - current version:", scriptInfo.version, "- latest version:", latestTag);
-
-  if(versionComp < 0) {
+  if(compare(scriptInfo.version, latestTag, "<")) {
     const dialog = await getVersionNotifDialog({ latestTag });
     await dialog.open();
     return;
   }
   return noUpdateFound();
 }
-
-/**
- * Crudely compares two semver version strings.  
- * The format is assumed to *always* be `MAJOR.MINOR.PATCH`, where each part is a number.
- * @returns Returns 1 if `a > b`, or -1 if `a < b`, or 0 if `a == b`
- */
-export function compareVersions(a: string, b: string) {
-  a = String(a).trim();
-  b = String(b).trim();
-
-  if([a, b].some(v => !v.match(/^\d+\.\d+\.\d+$/)))
-    throw new TypeError("Invalid version format, expected 'MAJOR.MINOR.PATCH'");
-
-  const pa = a.split(".");
-  const pb = b.split(".");
-  for(let i = 0; i < 3; i++) {
-    const na = Number(pa[i]);
-    const nb = Number(pb[i]);
-    if(na > nb)
-      return 1;
-    if(nb > na)
-      return -1;
-    if(!isNaN(na) && isNaN(nb))
-      return 1;
-    if(isNaN(na) && !isNaN(nb))
-      return -1;
-  }
-  return 0;
-}
-
-/**
- * Compares two version arrays.  
- * The format is assumed to *always* be `[MAJOR, MINOR, PATCH]`, where each part is a positive integer number.
- * @returns Returns 1 if `a > b`, or -1 if `a < b`, or 0 if `a == b`
- */
-export function compareVersionArrays(a: [major: number, minor: number, patch: number], b: [major: number, minor: number, patch: number]) {
-  if([a, b].some(v => !Array.isArray(v) || v.length !== 3 || v.some(iv => !Number.isInteger(iv) || iv < 0)))
-    throw new TypeError("Invalid version format, expected '[MAJOR, MINOR, PATCH]' consisting only of positive integers");
-  for(let i = 0; i < 3; i++) {
-    if(a[i] > b[i])
-      return 1;
-    if(b[i] > a[i])
-      return -1;
-  }
-  return 0;
-}

+ 18 - 4
src/index.ts

@@ -27,16 +27,19 @@ import {
   initVersionCheck,
 } from "./features";
 
+//#region console watermark
+
 {
   // console watermark with sexy gradient
   const styleGradient = "background: rgba(165, 38, 38, 1); background: linear-gradient(90deg, rgb(154, 31, 103) 0%, rgb(135, 31, 31) 40%, rgb(184, 64, 41) 100%);";
-  const styleCommon = "color: #fff; font-size: 1.5em; padding-left: 6px; padding-right: 6px;";
+  const styleCommon = "color: #fff; font-size: 1.3rem;";
 
   console.log();
   console.log(
-    `%c${scriptInfo.name}%cv${scriptInfo.version}%c\n\nBuild #${buildNumber} ─ ${scriptInfo.namespace}`,
-    `font-weight: bold; ${styleCommon} ${styleGradient}`,
-    `background-color: #333; ${styleCommon}`,
+    `%c${scriptInfo.name}%c${scriptInfo.version}%c • ${scriptInfo.namespace}%c\n\nBuild #${buildNumber}`,
+    `${styleCommon} ${styleGradient} font-weight: bold; padding-left: 6px; padding-right: 6px;`,
+    `${styleCommon} background-color: #333; padding-left: 8px; padding-right: 8px;`,
+    "color: #fff; font-size: 1.2rem;",
     "padding: initial;",
   );
   console.log([
@@ -44,6 +47,7 @@ import {
     "─ Lots of ambition and dedication",
     "─ My song metadata API: https://api.sv443.net/geniurl",
     "─ My userscript utility library: https://github.com/Sv443-Network/UserUtils",
+    "─ This library for semver comparison: https://github.com/omichelsen/compare-versions",
     "─ This tiny event listener library: https://github.com/ai/nanoevents",
     "─ This markdown parser library: https://github.com/markedjs/marked",
     "─ This fuzzy search library: https://github.com/krisk/Fuse",
@@ -51,6 +55,8 @@ import {
   console.log();
 }
 
+//#region preInit
+
 /** Stuff that needs to be called ASAP, before anything async happens */
 function preInit() {
   try {
@@ -69,6 +75,8 @@ function preInit() {
   }
 }
 
+//#region init
+
 async function init() {
   try {
     const domain = getDomain();
@@ -99,6 +107,8 @@ async function init() {
   }
 }
 
+//#region onDomLoad
+
 /** Called when the DOM has finished loading and can be queried and altered by the userscript */
 async function onDomLoad() {
   const domain = getDomain();
@@ -250,12 +260,16 @@ async function onDomLoad() {
   }
 }
 
+//#region insert css bundle
+
 /** Inserts the bundled CSS files imported throughout the script into a <style> element in the <head> */
 async function insertGlobalStyle() {
   if(!await addStyleFromResource("css-bundle"))
     error("Couldn't add global CSS bundle due to an error");
 }
 
+//#region dev menu cmds
+
 /** Registers dev commands using `GM.registerMenuCommand` */
 function registerDevMenuCommands() {
   if(mode !== "development")

+ 20 - 10
src/interface.ts

@@ -1,10 +1,11 @@
 import * as UserUtils from "@sv443-network/userutils";
+import * as compareVersions from "compare-versions";
 import { createNanoEvents } from "nanoevents";
 import { mode, branch, host, buildNumber, compressionFormat, scriptInfo } from "./constants";
 import { getResourceUrl, getSessionId, getVideoTime, log, setLocale, getLocale, hasKey, hasKeyFor, NanoEmitter, t, tp, type TrLocale, info, error, onInteraction, getThumbnailUrl, getBestThumbnailUrl } from "./utils";
 import { addSelectorListener } from "./observers";
 import { getFeatures, setFeatures } from "./config";
-import { compareVersionArrays, compareVersions, featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features";
+import { featInfo, fetchLyricsUrlTop, getLyricsCacheEntry, sanitizeArtists, sanitizeSong } from "./features";
 import { allSiteEvents, type SiteEventsMap } from "./siteEvents";
 import { LogLevel, type FeatureConfig, type FeatureInfo, type LyricsCacheEntry, type PluginDef, type PluginInfo, type PluginRegisterResult, type PluginDefResolvable, type PluginEventMap, type PluginItem, type BytmObject } from "./types";
 import { BytmDialog, createCircularBtn, createHotkeyInput, createToggleInput } from "./components";
@@ -97,29 +98,32 @@ const globalFuncs = {
   getLyricsCacheEntry,
   sanitizeArtists,
   sanitizeSong,
-  compareVersions,
-  compareVersionArrays,
   onInteraction,
   getThumbnailUrl,
   getBestThumbnailUrl,
+  createHotkeyInput,
+  createToggleInput,
+  createCircularBtn,
 };
 
 /** Initializes the BYTM interface */
 export function initInterface() {
   const props = {
+    // meta / constants
     mode,
     branch,
     host,
     buildNumber,
     compressionFormat,
     ...scriptInfo,
+    // functions
     ...globalFuncs,
-    UserUtils,
+    // classes
     NanoEmitter,
     BytmDialog,
-    createHotkeyInput,
-    createToggleInput,
-    createCircularBtn,
+    // libraries
+    UserUtils,
+    compareVersions,
   };
 
   for(const [key, value] of Object.entries(props))
@@ -279,15 +283,21 @@ export function getPluginInfo(...args: [token: string | undefined, pluginDefOrNa
 function validatePluginDef(pluginDef: Partial<PluginDef>) {
   const errors = [] as string[];
 
-  const addNoPropErr = (prop: string, type: string) =>
-    errors.push(t("plugin_validation_error_no_property", prop, type));
+  const addNoPropErr = (jsonPath: string, type: string) =>
+    errors.push(t("plugin_validation_error_no_property", jsonPath, type));
+
+  const addInvalidPropErr = (jsonPath: string, value: string, examples: string[]) =>
+    errors.push(tp("plugin_validation_error_invalid_property", examples, jsonPath, value, `'${examples.join("', '")}'`));
 
   // def.plugin and its properties:
   typeof pluginDef.plugin !== "object" && addNoPropErr("plugin", "object");
   const { plugin } = pluginDef;
   !plugin?.name && addNoPropErr("plugin.name", "string");
   !plugin?.namespace && addNoPropErr("plugin.namespace", "string");
-  !plugin?.version && addNoPropErr("plugin.version", "[major: number, minor: number, patch: number]");
+  if(typeof plugin?.version !== "string")
+    addNoPropErr("plugin.version", "MAJOR.MINOR.PATCH");
+  else if(!compareVersions.validateStrict(plugin.version))
+    addInvalidPropErr("plugin.version", plugin.version, ["0.0.1", "2.5.21-rc.1"]);
 
   return errors.length > 0 ? errors : undefined;
 }

+ 2 - 2
src/types.ts

@@ -120,8 +120,8 @@ export type PluginInfo = {
    * I recommend to set this value to a URL pointing to your homepage, or the author's username.
    */
   namespace: string;
-  /** Version of the plugin as an array containing three whole numbers: `[major_version, minor_version, patch_version]` */
-  version: [major: number, minor: number, patch: number];
+  /** Version of the plugin as a semver-compliant string */
+  version: string;
 };
 
 /** Minimum part of the PluginDef object needed to make up the resolvable plugin identifier */