Ver Fonte

ref: site events

Sven há 1 ano atrás
pai
commit
e3b9c61227
10 ficheiros alterados com 458 adições e 63 exclusões
  1. 1 0
      .gitignore
  2. 2 1
      README.md
  3. 0 0
      dist/BetterYTM.user.js
  4. 369 35
      package-lock.json
  5. 2 2
      package.json
  6. 5 3
      src/BetterYTM.user.ts
  7. 39 3
      src/features/layout.ts
  8. 6 3
      src/tools/serve.ts
  9. 30 14
      src/utils.ts
  10. 4 2
      tsconfig.json

+ 1 - 0
.gitignore

@@ -1,4 +1,5 @@
 node_modules/
+out/
 test.js
 test.ts
 *.map

+ 2 - 1
README.md

@@ -53,7 +53,8 @@ Once you have the extension, click this button to install the userscript:
 <!-- first column uses non-breaking space U+00A0 (' ') -->
 
 When using ViolentMonkey, after running the command `npm run watch`, open [`http://localhost:8710/dist/BetterYTM.user.js`](http://localhost:8710/dist/BetterYTM.user.js) and select the `Track local file` option.  
-This makes it so the userscript automatically updates when the code changes.
+This makes it so the userscript automatically updates when the code changes.  
+Note: the tab needs to stay open on Firefox or the script will not update itself.
 
 
 <br><br>

Diff do ficheiro suprimidas por serem muito extensas
+ 0 - 0
dist/BetterYTM.user.js


+ 369 - 35
package-lock.json

@@ -16,7 +16,7 @@
         "@types/greasemonkey": "^4.0.4",
         "@types/marked": "^5.0.0",
         "@types/node": "^20.2.4",
-        "@typescript-eslint/eslint-plugin": "^5.59.7",
+        "@typescript-eslint/eslint-plugin": "^5.59.8",
         "@typescript-eslint/parser": "^5.59.7",
         "eslint": "^7.32.0",
         "express": "^4.18.2",
@@ -503,15 +503,15 @@
       }
     },
     "node_modules/@typescript-eslint/eslint-plugin": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz",
-      "integrity": "sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz",
+      "integrity": "sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==",
       "dev": true,
       "dependencies": {
         "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/type-utils": "5.59.7",
-        "@typescript-eslint/utils": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.8",
+        "@typescript-eslint/type-utils": "5.59.8",
+        "@typescript-eslint/utils": "5.59.8",
         "debug": "^4.3.4",
         "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
@@ -536,6 +536,65 @@
         }
       }
     },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/scope-manager": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz",
+      "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/visitor-keys": "5.59.8"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/types": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+      "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+      "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/eslint-plugin/node_modules/eslint-visitor-keys": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@typescript-eslint/eslint-plugin/node_modules/ignore": {
       "version": "5.2.4",
       "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -590,13 +649,13 @@
       }
     },
     "node_modules/@typescript-eslint/type-utils": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz",
-      "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz",
+      "integrity": "sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==",
       "dev": true,
       "dependencies": {
-        "@typescript-eslint/typescript-estree": "5.59.7",
-        "@typescript-eslint/utils": "5.59.7",
+        "@typescript-eslint/typescript-estree": "5.59.8",
+        "@typescript-eslint/utils": "5.59.8",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
       },
@@ -616,6 +675,75 @@
         }
       }
     },
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/types": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+      "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/typescript-estree": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz",
+      "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/visitor-keys": "5.59.8",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.7",
+        "tsutils": "^3.21.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+      "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/type-utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@typescript-eslint/types": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.7.tgz",
@@ -657,17 +785,17 @@
       }
     },
     "node_modules/@typescript-eslint/utils": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz",
-      "integrity": "sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.8.tgz",
+      "integrity": "sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==",
       "dev": true,
       "dependencies": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@types/json-schema": "^7.0.9",
         "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/typescript-estree": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.8",
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/typescript-estree": "5.59.8",
         "eslint-scope": "^5.1.1",
         "semver": "^7.3.7"
       },
@@ -682,6 +810,92 @@
         "eslint": "^6.0.0 || ^7.0.0 || ^8.0.0"
       }
     },
+    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/scope-manager": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz",
+      "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/visitor-keys": "5.59.8"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/types": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+      "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/typescript-estree": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz",
+      "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/visitor-keys": "5.59.8",
+        "debug": "^4.3.4",
+        "globby": "^11.1.0",
+        "is-glob": "^4.0.3",
+        "semver": "^7.3.7",
+        "tsutils": "^3.21.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      },
+      "peerDependenciesMeta": {
+        "typescript": {
+          "optional": true
+        }
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/@typescript-eslint/visitor-keys": {
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+      "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+      "dev": true,
+      "dependencies": {
+        "@typescript-eslint/types": "5.59.8",
+        "eslint-visitor-keys": "^3.3.0"
+      },
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "type": "opencollective",
+        "url": "https://opencollective.com/typescript-eslint"
+      }
+    },
+    "node_modules/@typescript-eslint/utils/node_modules/eslint-visitor-keys": {
+      "version": "3.4.1",
+      "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+      "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+      "dev": true,
+      "engines": {
+        "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+      },
+      "funding": {
+        "url": "https://opencollective.com/eslint"
+      }
+    },
     "node_modules/@typescript-eslint/visitor-keys": {
       "version": "5.59.7",
       "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.7.tgz",
@@ -4786,15 +5000,15 @@
       }
     },
     "@typescript-eslint/eslint-plugin": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.7.tgz",
-      "integrity": "sha512-BL+jYxUFIbuYwy+4fF86k5vdT9lT0CNJ6HtwrIvGh0PhH8s0yy5rjaKH2fDCrz5ITHy07WCzVGNvAmjJh4IJFA==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-5.59.8.tgz",
+      "integrity": "sha512-JDMOmhXteJ4WVKOiHXGCoB96ADWg9q7efPWHRViT/f09bA8XOMLAVHHju3l0MkZnG1izaWXYmgvQcUjTRcpShQ==",
       "dev": true,
       "requires": {
         "@eslint-community/regexpp": "^4.4.0",
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/type-utils": "5.59.7",
-        "@typescript-eslint/utils": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.8",
+        "@typescript-eslint/type-utils": "5.59.8",
+        "@typescript-eslint/utils": "5.59.8",
         "debug": "^4.3.4",
         "grapheme-splitter": "^1.0.4",
         "ignore": "^5.2.0",
@@ -4803,6 +5017,38 @@
         "tsutils": "^3.21.0"
       },
       "dependencies": {
+        "@typescript-eslint/scope-manager": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz",
+          "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "@typescript-eslint/visitor-keys": "5.59.8"
+          }
+        },
+        "@typescript-eslint/types": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+          "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+          "dev": true
+        },
+        "@typescript-eslint/visitor-keys": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+          "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "eslint-visitor-keys": "^3.3.0"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "3.4.1",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+          "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+          "dev": true
+        },
         "ignore": {
           "version": "5.2.4",
           "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz",
@@ -4834,15 +5080,54 @@
       }
     },
     "@typescript-eslint/type-utils": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.7.tgz",
-      "integrity": "sha512-ozuz/GILuYG7osdY5O5yg0QxXUAEoI4Go3Do5xeu+ERH9PorHBPSdvD3Tjp2NN2bNLh1NJQSsQu2TPu/Ly+HaQ==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-5.59.8.tgz",
+      "integrity": "sha512-+5M518uEIHFBy3FnyqZUF3BMP+AXnYn4oyH8RF012+e7/msMY98FhGL5SrN29NQ9xDgvqCgYnsOiKp1VjZ/fpA==",
       "dev": true,
       "requires": {
-        "@typescript-eslint/typescript-estree": "5.59.7",
-        "@typescript-eslint/utils": "5.59.7",
+        "@typescript-eslint/typescript-estree": "5.59.8",
+        "@typescript-eslint/utils": "5.59.8",
         "debug": "^4.3.4",
         "tsutils": "^3.21.0"
+      },
+      "dependencies": {
+        "@typescript-eslint/types": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+          "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+          "dev": true
+        },
+        "@typescript-eslint/typescript-estree": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz",
+          "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "@typescript-eslint/visitor-keys": "5.59.8",
+            "debug": "^4.3.4",
+            "globby": "^11.1.0",
+            "is-glob": "^4.0.3",
+            "semver": "^7.3.7",
+            "tsutils": "^3.21.0"
+          }
+        },
+        "@typescript-eslint/visitor-keys": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+          "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "eslint-visitor-keys": "^3.3.0"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "3.4.1",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+          "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+          "dev": true
+        }
       }
     },
     "@typescript-eslint/types": {
@@ -4867,19 +5152,68 @@
       }
     },
     "@typescript-eslint/utils": {
-      "version": "5.59.7",
-      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.7.tgz",
-      "integrity": "sha512-yCX9WpdQKaLufz5luG4aJbOpdXf/fjwGMcLFXZVPUz3QqLirG5QcwwnIHNf8cjLjxK4qtzTO8udUtMQSAToQnQ==",
+      "version": "5.59.8",
+      "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-5.59.8.tgz",
+      "integrity": "sha512-Tr65630KysnNn9f9G7ROF3w1b5/7f6QVCJ+WK9nhIocWmx9F+TmCAcglF26Vm7z8KCTwoKcNEBZrhlklla3CKg==",
       "dev": true,
       "requires": {
         "@eslint-community/eslint-utils": "^4.2.0",
         "@types/json-schema": "^7.0.9",
         "@types/semver": "^7.3.12",
-        "@typescript-eslint/scope-manager": "5.59.7",
-        "@typescript-eslint/types": "5.59.7",
-        "@typescript-eslint/typescript-estree": "5.59.7",
+        "@typescript-eslint/scope-manager": "5.59.8",
+        "@typescript-eslint/types": "5.59.8",
+        "@typescript-eslint/typescript-estree": "5.59.8",
         "eslint-scope": "^5.1.1",
         "semver": "^7.3.7"
+      },
+      "dependencies": {
+        "@typescript-eslint/scope-manager": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-5.59.8.tgz",
+          "integrity": "sha512-/w08ndCYI8gxGf+9zKf1vtx/16y8MHrZs5/tnjHhMLNSixuNcJavSX4wAiPf4aS5x41Es9YPCn44MIe4cxIlig==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "@typescript-eslint/visitor-keys": "5.59.8"
+          }
+        },
+        "@typescript-eslint/types": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-5.59.8.tgz",
+          "integrity": "sha512-+uWuOhBTj/L6awoWIg0BlWy0u9TyFpCHrAuQ5bNfxDaZ1Ppb3mx6tUigc74LHcbHpOHuOTOJrBoAnhdHdaea1w==",
+          "dev": true
+        },
+        "@typescript-eslint/typescript-estree": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-5.59.8.tgz",
+          "integrity": "sha512-Jy/lPSDJGNow14vYu6IrW790p7HIf/SOV1Bb6lZ7NUkLc2iB2Z9elESmsaUtLw8kVqogSbtLH9tut5GCX1RLDg==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "@typescript-eslint/visitor-keys": "5.59.8",
+            "debug": "^4.3.4",
+            "globby": "^11.1.0",
+            "is-glob": "^4.0.3",
+            "semver": "^7.3.7",
+            "tsutils": "^3.21.0"
+          }
+        },
+        "@typescript-eslint/visitor-keys": {
+          "version": "5.59.8",
+          "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-5.59.8.tgz",
+          "integrity": "sha512-pJhi2ms0x0xgloT7xYabil3SGGlojNNKjK/q6dB3Ey0uJLMjK2UDGJvHieiyJVW/7C3KI+Z4Q3pEHkm4ejA+xQ==",
+          "dev": true,
+          "requires": {
+            "@typescript-eslint/types": "5.59.8",
+            "eslint-visitor-keys": "^3.3.0"
+          }
+        },
+        "eslint-visitor-keys": {
+          "version": "3.4.1",
+          "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz",
+          "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==",
+          "dev": true
+        }
       }
     },
     "@typescript-eslint/visitor-keys": {

+ 2 - 2
package.json

@@ -13,7 +13,7 @@
     "serve": "npm run node-ts -- ./src/tools/serve.ts",
     "serve-old": "http-server -s -c 5 -p 8710 .",
     "watch": "nodemon --exec \"npm run build && npm run serve\"",
-    "lint": "eslint .",
+    "lint": "tsc --noEmit && eslint .",
     "node-ts": "node --no-warnings=ExperimentalWarning --enable-source-maps --loader ts-node/esm"
   },
   "repository": {
@@ -36,7 +36,7 @@
     "@types/greasemonkey": "^4.0.4",
     "@types/marked": "^5.0.0",
     "@types/node": "^20.2.4",
-    "@typescript-eslint/eslint-plugin": "^5.59.7",
+    "@typescript-eslint/eslint-plugin": "^5.59.8",
     "@typescript-eslint/parser": "^5.59.7",
     "eslint": "^7.32.0",
     "express": "^4.18.2",

+ 5 - 3
src/BetterYTM.user.ts

@@ -1,9 +1,9 @@
 import { getFeatures } from "./config";
 import { dbg, info } from "./constants";
-import { getDomain } from "./utils";
+import { getDomain, initSiteEvents } from "./utils";
 import {
   // layout
-  addMediaCtrlGeniusBtn, addQueueButtons, addWatermark,
+  addMediaCtrlGeniusBtn, initQueueButtons, addWatermark,
   preInitLayout, removeUpgradeTab, setVolSliderSize,
   setVolSliderStep,
   // lyrics
@@ -40,6 +40,8 @@ import {
 
     dbg && console.log(`BetterYTM: Initializing features for domain '${domain}'`);
 
+    initSiteEvents();
+
     try {
       if(domain === "ytm") {
         if(features.arrowKeySupport)
@@ -55,7 +57,7 @@ import {
           addMediaCtrlGeniusBtn();
 
         if(features.queueButtons)
-          addQueueButtons();
+          initQueueButtons();
 
         if(typeof features.volumeSliderSize === "number")
           setVolSliderSize();

+ 39 - 3
src/features/layout.ts

@@ -1,6 +1,6 @@
 import { dbg, info, triesInterval, triesLimit } from "../constants";
 import { getFeatures } from "../config";
-import { addGlobalStyle, insertAfter } from "../utils";
+import { addGlobalStyle, insertAfter, siteEvents } from "../utils";
 import type { FeatureConfig } from "../types";
 import { openMenu } from "./menu";
 
@@ -103,6 +103,42 @@ export function setVolSliderStep() {
 
 //#MARKER queue buttons
 
-export function addQueueButtons() {
-  void "TODO";
+export function initQueueButtons() {
+  siteEvents.on("queueChanged", (evt) => {
+    for(const queueItm of ((evt.data as HTMLElement).childNodes as NodeListOf<HTMLElement>)) {
+      if(!queueItm.dataset["bytm-has-queue-btns"])
+        addQueueButtons(queueItm);
+    }
+  });
+
+  const queueBtnsStyle = `\
+.side-panel.modular ytmusic-player-queue-item .song-info.ytmusic-player-queue-item {
+  position: relative;
+}
+.side-panel.modular ytmusic-player-queue-item .song-info .bytm-queue-btn-container {
+  display: none;
+  position: absolute;
+  right: 0;
+}
+.side-panel.modular ytmusic-player-queue-item:hover .song-info .bytm-queue-btn-container {
+  display: inline-block;
+}`;
+  addGlobalStyle(queueBtnsStyle, "queue-btns");
+
+  const queueItems = document.querySelectorAll("#contents.ytmusic-player-queue > ytmusic-player-queue-item");
+  if(queueItems.length === 0)
+    return;
+
+  queueItems.forEach(itm => addQueueButtons(itm as HTMLElement));
+}
+
+function addQueueButtons(queueItem: HTMLElement) {
+  console.log("Add queue btns:", queueItem);
+
+  const queueBtnsCont = document.createElement("div");
+  queueBtnsCont.className = "bytm-queue-btn-container";
+  queueBtnsCont.innerText = "ayo";
+
+  const songInfo = queueItem.querySelector(".song-info")!;
+  songInfo.appendChild(queueBtnsCont);
 }

+ 6 - 3
src/tools/serve.ts

@@ -9,15 +9,17 @@ const enableLogging = false;
 const packedScriptDir = "dist";
 /** Name of the packed userscript file */
 const packedScriptName = "BetterYTM.user.js";
+/** Whether to make a bell sound (in some terminals) when the userscript is ready to be fetched */
+const ringBell = true;
 
-app.use((req, res, next) => {
+app.use((_req, _res, next) => {
   enableLogging && process.stdout.write("*");
   next();
 });
 
-app.use((err: unknown, req: Request, res: Response, next: NextFunction) => {
+app.use((err: unknown, _req: Request, _res: Response, _next: NextFunction) => {
   if(typeof err === "string" || err instanceof Error)
-    console.error(`\x1b[31mError in dev server:\x1b[0m\n`, err);
+    console.error("\x1b[31mError in dev server:\x1b[0m\n", err);
 });
 
 app.use(express.static(packedScriptDir, {
@@ -31,4 +33,5 @@ app.listen(devServerPort, "0.0.0.0", () => {
     process.stdout.write("\nRequests: ");
   else
     console.log("\x1b[2m(request logging is disabled)\x1b[0m");
+  ringBell && process.stdout.write("\u0007");
 });

+ 30 - 14
src/utils.ts

@@ -2,6 +2,8 @@ import { EventEmitter, EventHandler } from "@billjs/event-emitter";
 import { dbg } from "./constants";
 import type { Domain } from "./types";
 
+//#SECTION BYTM-specific
+
 /**
  * Returns the current domain as a constant string representation
  * @throws Throws if script runs on an unexpected website
@@ -41,6 +43,8 @@ export function getVideoTime() {
   }
 }
 
+//#SECTION DOM
+
 /**
  * Inserts `afterNode` as a sibling just after the provided `beforeNode`
  * @param beforeNode
@@ -59,7 +63,7 @@ export function insertAfter(beforeNode: HTMLElement, afterNode: HTMLElement) {
  */
 export function addGlobalStyle(style: string, ref?: string) {
   if(typeof ref !== "string" || ref.length === 0)
-    ref = String(Math.floor(Math.random() * 10000));
+    ref = String(Math.floor(Math.random() * 10_000));
 
   const styleElem = document.createElement("style");
   styleElem.id = `betterytm-style-${ref}`;
@@ -69,21 +73,33 @@ export function addGlobalStyle(style: string, ref?: string) {
   dbg && console.log(`BetterYTM: Inserted global style with ref '${ref}':`, styleElem);
 }
 
-interface SiteEvents extends EventEmitter {
-  /** Emitted whenever the site "changes" in a major way */
-  on(event: "change", handler: EventHandler): boolean;
+//#SECTION site events
+
+export interface SiteEvents extends EventEmitter {
+  /** Emitted whenever a song is added to or removed from the queue */
+  on(event: "queueChanged", listener: EventHandler): boolean;
 }
 
-class SiteEvents extends EventEmitter {
-  constructor() {
-    super();
-    document.addEventListener("DOMContentLoaded", this.initObserver);
-  }
+export const siteEvents = new EventEmitter() as SiteEvents;
 
-  initObserver() {
-    void "TODO: observe";
-  }
+let observers: MutationObserver[] = [];
+
+export function initSiteEvents() {
+  const queueObserver = new MutationObserver(([ record ]) => {
+    if(record.addedNodes.length > 0 || record.removedNodes.length > 0)
+      siteEvents.fire("queueChanged", record.target);
+  });
+  // only observe added or removed elements
+  queueObserver.observe(document.querySelector(".side-panel.modular #contents.ytmusic-player-queue")!, {
+    childList: true,
+  });
+
+  observers = [
+    queueObserver,
+  ];
 }
 
-/** Emits all site-specific events */
-export const siteEvents = new SiteEvents();
+export function removeObservers() {
+  observers.forEach((observer) => observer.disconnect());
+  observers = [];
+}

+ 4 - 2
tsconfig.json

@@ -1,9 +1,9 @@
 {
   "compilerOptions": {
     "module": "ESNext",
-    "moduleResolution": "bundler",
+    "moduleResolution": "node",
     "target": "ES2016",
-    "outDir": ".",
+    "outDir": "dist/out",
     "lib": [
       "DOM",
       "DOM.Iterable"
@@ -23,6 +23,8 @@
   },
   "exclude": [
     "**/*.js",
+    "dist/**",
+    "rollup.config.cjs",
   ],
   "typeAcquisition": {
     "enable": true,

Alguns ficheiros não foram mostrados porque muitos ficheiros mudaram neste diff