diff --git a/CHANGELOG.md b/CHANGELOG.md
index 02228277..526489c0 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,41 +1,33 @@
## [1.11.8](https://github.com/VampireChicken12/youtube-enhancer/compare/v1.11.7...v1.11.8) (2023-11-16)
-
### Bug Fixes
-* Fix on screen display color visibility in settings ([95ad5e9](https://github.com/VampireChicken12/youtube-enhancer/commit/95ad5e9d4ced61c2b8301ba10f14c2d5401cc2d0))
-* Fix selected option text being white on light mode ([2781492](https://github.com/VampireChicken12/youtube-enhancer/commit/278149232e9fc34ef46bc9e9f56aa11fb24e9214))
-
-
-
-
+- Fix on screen display color visibility in settings ([95ad5e9](https://github.com/VampireChicken12/youtube-enhancer/commit/95ad5e9d4ced61c2b8301ba10f14c2d5401cc2d0))
+- Fix selected option text being white on light mode ([2781492](https://github.com/VampireChicken12/youtube-enhancer/commit/278149232e9fc34ef46bc9e9f56aa11fb24e9214))
## Release Artifacts
-| File Name | SHA-256 Hash |
-| :--- | :---: |
-| youtube-enhancer-v1.11.8-Chrome.zip | 87600763560a4935b8369974d9757c3350f572019ada31d8ec5a79fcdfdcf7a6 |
+
+| File Name | SHA-256 Hash |
+| :------------------------------------ | :--------------------------------------------------------------: |
+| youtube-enhancer-v1.11.8-Chrome.zip | 87600763560a4935b8369974d9757c3350f572019ada31d8ec5a79fcdfdcf7a6 |
| youtube-enhancer-v1.11.8-Chromium.zip | 87600763560a4935b8369974d9757c3350f572019ada31d8ec5a79fcdfdcf7a6 |
-| youtube-enhancer-v1.11.8-Edge.zip | 87600763560a4935b8369974d9757c3350f572019ada31d8ec5a79fcdfdcf7a6 |
-| youtube-enhancer-v1.11.8-Firefox.zip | 10acae4d7e87dc4b55258a83d6f63c5c7ca8a25eadeae7e822ab78647f3940f6 |
+| youtube-enhancer-v1.11.8-Edge.zip | 87600763560a4935b8369974d9757c3350f572019ada31d8ec5a79fcdfdcf7a6 |
+| youtube-enhancer-v1.11.8-Firefox.zip | 10acae4d7e87dc4b55258a83d6f63c5c7ca8a25eadeae7e822ab78647f3940f6 |
## [1.11.7](https://github.com/VampireChicken12/youtube-enhancer/compare/v1.11.6...v1.11.7) (2023-11-15)
-
### Bug Fixes
-* fix bug caused by previous bug fix ([68225b6](https://github.com/VampireChicken12/youtube-enhancer/commit/68225b685a4900200c0f3d045972e1b31ee80955))
-
-
-
-
+- fix bug caused by previous bug fix ([68225b6](https://github.com/VampireChicken12/youtube-enhancer/commit/68225b685a4900200c0f3d045972e1b31ee80955))
## Release Artifacts
-| File Name | SHA-256 Hash |
-| :--- | :---: |
-| youtube-enhancer-v1.11.7-Chrome.zip | 81372739f88cd91097ee422a48a32af264d3df510e2fa0d9c7a53133bdcb9184 |
+
+| File Name | SHA-256 Hash |
+| :------------------------------------ | :--------------------------------------------------------------: |
+| youtube-enhancer-v1.11.7-Chrome.zip | 81372739f88cd91097ee422a48a32af264d3df510e2fa0d9c7a53133bdcb9184 |
| youtube-enhancer-v1.11.7-Chromium.zip | 81372739f88cd91097ee422a48a32af264d3df510e2fa0d9c7a53133bdcb9184 |
-| youtube-enhancer-v1.11.7-Edge.zip | 81372739f88cd91097ee422a48a32af264d3df510e2fa0d9c7a53133bdcb9184 |
-| youtube-enhancer-v1.11.7-Firefox.zip | 23b446cb33e9cbe052a7f823f3e32a3f0522d17c63fea9814fe3bd237a9ef49e |
+| youtube-enhancer-v1.11.7-Edge.zip | 81372739f88cd91097ee422a48a32af264d3df510e2fa0d9c7a53133bdcb9184 |
+| youtube-enhancer-v1.11.7-Firefox.zip | 23b446cb33e9cbe052a7f823f3e32a3f0522d17c63fea9814fe3bd237a9ef49e |
## [1.11.6](https://github.com/VampireChicken12/youtube-enhancer/compare/v1.11.5...v1.11.6) (2023-11-15)
diff --git a/README.md b/README.md
index 5e967a79..0d6b769c 100755
--- a/README.md
+++ b/README.md
@@ -45,6 +45,8 @@ YouTube Enhancer is a browser extension that aims to improve your YouTube experi
- **Enable Scroll Wheel Volume Control:** Control video volume with your mouse's scroll wheel for quick and easy adjustments.
+- **Scroll Wheel Volume Control Modifier Key**: Optionally, enable a modifier key to adjust the volume only when the specified key is held down during scroll wheel actions.
+
- **OSD Color:** Choose the color of the On-Screen Display (OSD) for volume control.
- **OSD Type:** Define the type of OSD, including text, line, round, or no display.
diff --git a/package-lock.json b/package-lock.json
index 7b85d819..48df8f68 100644
--- a/package-lock.json
+++ b/package-lock.json
@@ -1,12 +1,12 @@
{
"name": "youtube-enhancer",
- "version": "1.11.5",
+ "version": "1.11.8",
"lockfileVersion": 3,
"requires": true,
"packages": {
"": {
"name": "youtube-enhancer",
- "version": "1.11.5",
+ "version": "1.11.8",
"license": "MIT",
"dependencies": {
"@formkit/auto-animate": "^0.8.1",
@@ -689,9 +689,9 @@
}
},
"node_modules/@eslint/js": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz",
- "integrity": "sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.54.0.tgz",
+ "integrity": "sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==",
"dev": true,
"engines": {
"node": "^12.22.0 || ^14.17.0 || >=16.0.0"
@@ -1826,9 +1826,9 @@
"dev": true
},
"node_modules/@types/node": {
- "version": "20.9.0",
- "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz",
- "integrity": "sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==",
+ "version": "20.9.2",
+ "resolved": "https://registry.npmjs.org/@types/node/-/node-20.9.2.tgz",
+ "integrity": "sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==",
"devOptional": true,
"dependencies": {
"undici-types": "~5.26.4"
@@ -2083,23 +2083,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@typescript-eslint/scope-manager": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz",
- "integrity": "sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "6.10.0",
- "@typescript-eslint/visitor-keys": "6.10.0"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
"node_modules/@typescript-eslint/type-utils": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz",
@@ -2184,46 +2167,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@typescript-eslint/types": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz",
- "integrity": "sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==",
- "dev": true,
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
- "node_modules/@typescript-eslint/typescript-estree": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz",
- "integrity": "sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "6.10.0",
- "@typescript-eslint/visitor-keys": "6.10.0",
- "debug": "^4.3.4",
- "globby": "^11.1.0",
- "is-glob": "^4.0.3",
- "semver": "^7.5.4",
- "ts-api-utils": "^1.0.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- },
- "peerDependenciesMeta": {
- "typescript": {
- "optional": true
- }
- }
- },
"node_modules/@typescript-eslint/utils": {
"version": "6.11.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.11.0.tgz",
@@ -2323,23 +2266,6 @@
"url": "https://opencollective.com/typescript-eslint"
}
},
- "node_modules/@typescript-eslint/visitor-keys": {
- "version": "6.10.0",
- "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz",
- "integrity": "sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==",
- "dev": true,
- "dependencies": {
- "@typescript-eslint/types": "6.10.0",
- "eslint-visitor-keys": "^3.4.1"
- },
- "engines": {
- "node": "^16.0.0 || >=18.0.0"
- },
- "funding": {
- "type": "opencollective",
- "url": "https://opencollective.com/typescript-eslint"
- }
- },
"node_modules/@ungap/structured-clone": {
"version": "1.2.0",
"resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz",
@@ -2347,15 +2273,15 @@
"dev": true
},
"node_modules/@vitejs/plugin-react-swc": {
- "version": "3.4.1",
- "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.1.tgz",
- "integrity": "sha512-7YQOQcVV5x1luD8nkbCDdyYygFvn1hjqJk68UvNAzY2QG4o4N5EwAhLLFNOcd1HrdMwDl0VElP8VutoWf9IvJg==",
+ "version": "3.5.0",
+ "resolved": "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz",
+ "integrity": "sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==",
"dev": true,
"dependencies": {
- "@swc/core": "^1.3.95"
+ "@swc/core": "^1.3.96"
},
"peerDependencies": {
- "vite": "^4"
+ "vite": "^4 || ^5"
}
},
"node_modules/abbrev": {
@@ -4153,15 +4079,15 @@
}
},
"node_modules/eslint": {
- "version": "8.53.0",
- "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz",
- "integrity": "sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==",
+ "version": "8.54.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.54.0.tgz",
+ "integrity": "sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==",
"dev": true,
"dependencies": {
"@eslint-community/eslint-utils": "^4.2.0",
"@eslint-community/regexpp": "^4.6.1",
"@eslint/eslintrc": "^2.1.3",
- "@eslint/js": "8.53.0",
+ "@eslint/js": "8.54.0",
"@humanwhocodes/config-array": "^0.11.13",
"@humanwhocodes/module-importer": "^1.0.1",
"@nodelib/fs.walk": "^1.2.8",
@@ -4370,9 +4296,9 @@
}
},
"node_modules/eslint-plugin-perfectionist": {
- "version": "2.3.0",
- "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.3.0.tgz",
- "integrity": "sha512-T/1HOysrsyExPr/N5apy3XFhejYqIturtejlSbTGy0WCw5dt72FDT92NOvRRKJvx8lftZDJ8AEIs5nHk9Pfa9Q==",
+ "version": "2.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.4.0.tgz",
+ "integrity": "sha512-til+vyf56wAUgFv5guBA1Zo5lTw9xj2kCeK/g+9NBtsRy1rkGrlqnvxYNuFExcK3VsPhUUtx5UdScEDz9ahQ5Q==",
"dev": true,
"dependencies": {
"@typescript-eslint/utils": "^6.10.0",
@@ -5386,11 +5312,12 @@
}
},
"node_modules/import-from-esm": {
- "version": "1.2.1",
- "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.2.1.tgz",
- "integrity": "sha512-Nly5Ab75rWZmOwtMa0B0NQNnHGcHOQ2zkU/bVENwK2lbPq+kamPDqNKNJ0hF7w7lR/ETD5nGgJq0XbofsZpYCA==",
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.3.3.tgz",
+ "integrity": "sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==",
"dev": true,
"dependencies": {
+ "debug": "^4.3.4",
"import-meta-resolve": "^4.0.0"
},
"engines": {
@@ -11026,9 +10953,9 @@
}
},
"node_modules/semantic-release": {
- "version": "22.0.7",
- "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.7.tgz",
- "integrity": "sha512-Stx23Hjn7iU8GOAlhG3pHlR7AoNEahj9q7lKBP0rdK2BasGtJ4AWYh3zm1u3SCMuFiA8y4CE/Gu4RGKau1WiaQ==",
+ "version": "22.0.8",
+ "resolved": "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.8.tgz",
+ "integrity": "sha512-55rb31jygqIYsGU/rY+gXXm2fnxBIWo9azOjxbqKsPnq7p70zwZ5v+xnD7TxJC+zvS3sy1eHLGXYWCaX3WI76A==",
"dev": true,
"dependencies": {
"@semantic-release/commit-analyzer": "^11.0.0",
@@ -11047,6 +10974,7 @@
"git-log-parser": "^1.2.0",
"hook-std": "^3.0.0",
"hosted-git-info": "^7.0.0",
+ "import-from-esm": "^1.3.1",
"lodash-es": "^4.17.21",
"marked": "^9.0.0",
"marked-terminal": "^6.0.0",
diff --git a/package.json b/package.json
index 7ea1f200..461f70a1 100644
--- a/package.json
+++ b/package.json
@@ -1,75 +1,75 @@
{
- "name": "youtube-enhancer",
- "author": {
- "name": "VampireChicken12"
- },
- "displayName": "YouTube Enhancer",
- "version": "1.11.8",
- "description": "YouTube Enhancer is a simple extension that adds some useful features to YouTube.",
- "license": "MIT",
- "repository": {
- "type": "git",
- "url": "https://github.com/VampireChicken12/youtube-enhancer.git"
- },
- "scripts": {
- "build": "vite build",
- "dev": "nodemon",
- "format": "prettier --write .",
- "lint": "eslint --fix"
- },
- "type": "module",
- "dependencies": {
- "@formkit/auto-animate": "^0.8.1",
- "i18next": "^23.7.3",
- "react": "^18.2.0",
- "react-dom": "^18.2.0",
- "vite-plugin-css-injected-by-js": "^3.3.0",
- "webextension-polyfill": "^0.10.0"
- },
- "devDependencies": {
- "@semantic-release/changelog": "^6.0.3",
- "@semantic-release/exec": "^6.0.3",
- "@semantic-release/git": "^10.0.1",
- "@thedutchcoder/postcss-rem-to-px": "^0.0.2",
- "@total-typescript/ts-reset": "^0.5.1",
- "@types/archiver": "^6.0.1",
- "@types/chrome": "^0.0.251",
- "@types/node": "^20.9.0",
- "@types/react": "^18.2.37",
- "@types/react-dom": "^18.2.15",
- "@types/webextension-polyfill": "^0.10.6",
- "@types/youtube-player": "^5.5.10",
- "@typescript-eslint/eslint-plugin": "^6.10.0",
- "@typescript-eslint/parser": "^6.10.0",
- "@vitejs/plugin-react-swc": "^3.4.1",
- "archiver": "^6.0.1",
- "autoprefixer": "^10.4.16",
- "clsx": "^2.0.0",
- "concurrently": "^8.2.2",
- "eslint": "^8.53.0",
- "eslint-config-prettier": "^9.0.0",
- "eslint-plugin-import": "^2.29.0",
- "eslint-plugin-jsx-a11y": "^6.8.0",
- "eslint-plugin-no-secrets": "^0.8.9",
- "eslint-plugin-perfectionist": "^2.3.0",
- "eslint-plugin-prettier": "^5.0.1",
- "eslint-plugin-promise": "^6.1.1",
- "eslint-plugin-react": "^7.33.2",
- "eslint-plugin-react-hooks": "^4.6.0",
- "eslint-plugin-tailwindcss": "^3.13.0",
- "fs-extra": "^11.1.1",
- "get-installed-browsers": "^0.1.7",
- "nodemon": "^3.0.1",
- "postcss": "^8.4.31",
- "prettier": "^3.0.3",
- "semantic-release": "^22.0.7",
- "tailwind-merge": "^2.0.0",
- "tailwindcss": "^3.3.5",
- "ts-json-as-const": "^1.0.7",
- "ts-node": "^10.9.1",
- "typescript": "^5.2.2",
- "vite": "^4.5.0",
- "zod": "^3.22.4",
- "zod-error": "^1.5.0"
- }
-}
\ No newline at end of file
+ "name": "youtube-enhancer",
+ "author": {
+ "name": "VampireChicken12"
+ },
+ "displayName": "YouTube Enhancer",
+ "version": "1.11.8",
+ "description": "YouTube Enhancer is a simple extension that adds some useful features to YouTube.",
+ "license": "MIT",
+ "repository": {
+ "type": "git",
+ "url": "https://github.com/VampireChicken12/youtube-enhancer.git"
+ },
+ "scripts": {
+ "build": "vite build",
+ "dev": "nodemon",
+ "format": "prettier --write .",
+ "lint": "eslint --fix"
+ },
+ "type": "module",
+ "dependencies": {
+ "@formkit/auto-animate": "^0.8.1",
+ "i18next": "^23.7.3",
+ "react": "^18.2.0",
+ "react-dom": "^18.2.0",
+ "vite-plugin-css-injected-by-js": "^3.3.0",
+ "webextension-polyfill": "^0.10.0"
+ },
+ "devDependencies": {
+ "@semantic-release/changelog": "^6.0.3",
+ "@semantic-release/exec": "^6.0.3",
+ "@semantic-release/git": "^10.0.1",
+ "@thedutchcoder/postcss-rem-to-px": "^0.0.2",
+ "@total-typescript/ts-reset": "^0.5.1",
+ "@types/archiver": "^6.0.1",
+ "@types/chrome": "^0.0.251",
+ "@types/node": "^20.9.0",
+ "@types/react": "^18.2.37",
+ "@types/react-dom": "^18.2.15",
+ "@types/webextension-polyfill": "^0.10.6",
+ "@types/youtube-player": "^5.5.10",
+ "@typescript-eslint/eslint-plugin": "^6.10.0",
+ "@typescript-eslint/parser": "^6.10.0",
+ "@vitejs/plugin-react-swc": "^3.4.1",
+ "archiver": "^6.0.1",
+ "autoprefixer": "^10.4.16",
+ "clsx": "^2.0.0",
+ "concurrently": "^8.2.2",
+ "eslint": "^8.53.0",
+ "eslint-config-prettier": "^9.0.0",
+ "eslint-plugin-import": "^2.29.0",
+ "eslint-plugin-jsx-a11y": "^6.8.0",
+ "eslint-plugin-no-secrets": "^0.8.9",
+ "eslint-plugin-perfectionist": "^2.3.0",
+ "eslint-plugin-prettier": "^5.0.1",
+ "eslint-plugin-promise": "^6.1.1",
+ "eslint-plugin-react": "^7.33.2",
+ "eslint-plugin-react-hooks": "^4.6.0",
+ "eslint-plugin-tailwindcss": "^3.13.0",
+ "fs-extra": "^11.1.1",
+ "get-installed-browsers": "^0.1.7",
+ "nodemon": "^3.0.1",
+ "postcss": "^8.4.31",
+ "prettier": "^3.0.3",
+ "semantic-release": "^22.0.7",
+ "tailwind-merge": "^2.0.0",
+ "tailwindcss": "^3.3.5",
+ "ts-json-as-const": "^1.0.7",
+ "ts-node": "^10.9.1",
+ "typescript": "^5.2.2",
+ "vite": "^4.5.0",
+ "zod": "^3.22.4",
+ "zod-error": "^1.5.0"
+ }
+}
diff --git a/public/locales/en-US.json b/public/locales/en-US.json
index cfa87117..443dc2cd 100644
--- a/public/locales/en-US.json
+++ b/public/locales/en-US.json
@@ -59,7 +59,7 @@
"title": "Miscellaneous settings",
"features": {
"rememberLastVolume": {
- "title": "Remembers the volume you were watching at and sets it as the volume when you open a new video",
+ "title": "Remembers the volume of the last video you were watching and sets it when you open a new video",
"label": "Remember last volume"
},
"maximizePlayerButton": {
@@ -81,6 +81,10 @@
"hideScrollbar": {
"title": "Hides the pages scrollbar",
"label": "Enable hide scrollbar"
+ },
+ "automaticTheaterMode": {
+ "title": "Automatically enables theater mode when you load a video",
+ "label": "Enable automatic theater mode"
}
}
},
@@ -90,16 +94,16 @@
"title": "Lets you use the scroll wheel to control the volume of the video you're watching",
"label": "Enable scroll wheel volume control"
},
- "osdColor": { "title": "The color of the On Screen Display", "label": "OSD color" },
- "osdType": { "title": "The type of On Screen Display", "label": "OSD type" },
- "osdPosition": { "title": "The position of the On Screen Display", "label": "OSD position" },
- "osdOpacity": {
- "title": "The opacity of the On Screen Display",
- "label": "OSD opacity"
+ "osdColor": { "title": "Select the color for the On-Screen Display", "label": "OSD Color" },
+ "osdType": { "title": "Select the style of On-Screen Display", "label": "OSD Type" },
+ "osdPosition": { "title": "Select the position of the On-Screen Display", "label": "OSD Position" },
+ "osdOpacity": { "title": "Adjust the transparency of the On-Screen Display", "label": "OSD Opacity" },
+ "osdVolumeAdjustmentSteps": { "title": "Adjust the volume change per scroll", "label": "Volume Change Per Scroll" },
+ "osdHide": { "title": "Specify the time, in milliseconds, before automatically hiding the OSD", "label": "Hide Delay" },
+ "osdPadding": {
+ "title": "Adjust the spacing around the on-screen display (OSD) in pixels. This applies specifically to corner OSD.",
+ "label": "Padding"
},
- "osdVolumeAdjustmentSteps": { "title": "The amount to adjust volume per scroll", "label": "Amount to adjust" },
- "osdHide": { "title": "The amount of milliseconds to wait before hiding the OSD", "label": "Time to hide" },
- "osdPadding": { "title": "The amount of padding to add to the OSD (in pixels, only applies to corner OSD)", "label": "Padding" },
"onScreenDisplay": {
"colors": {
"red": "Red",
@@ -124,6 +128,22 @@
"line": "Line",
"round": "Round"
}
+ },
+ "modifierKey": {
+ "title": "Scroll Wheel Modifier Key",
+ "enable": {
+ "title": "Press a modifier key to enable volume adjustment with the scroll wheel.",
+ "label": "Enable modifier key"
+ },
+ "options": {
+ "altKey": "{{KEY}} key",
+ "ctrlKey": "{{KEY}} key",
+ "shiftKey": "{{KEY}} key"
+ },
+ "select": {
+ "label": "Modifier key",
+ "title": "The modifier key to use"
+ }
}
},
"automaticQuality": {
@@ -173,11 +193,6 @@
"label": "Screenshot format",
"title": "The format to save the screenshot in"
},
- "format": {
- "png": "PNG",
- "jpeg": "JPEG",
- "webp": "WebP"
- },
"saveAs": {
"file": "File",
"clipboard": "Clipboard"
diff --git a/public/locales/en-US.json.d.ts b/public/locales/en-US.json.d.ts
index 05957ae0..c3d133b8 100644
--- a/public/locales/en-US.json.d.ts
+++ b/public/locales/en-US.json.d.ts
@@ -74,6 +74,10 @@ interface EnUS {
};
miscellaneous: {
features: {
+ automaticTheaterMode: {
+ label: "Enable automatic theater mode";
+ title: "Automatically enables theater mode when you load a video";
+ };
hideScrollbar: { label: "Enable hide scrollbar"; title: "Hides the pages scrollbar" };
loopButton: {
label: "Enable loop button";
@@ -89,7 +93,7 @@ interface EnUS {
};
rememberLastVolume: {
label: "Remember last volume";
- title: "Remembers the volume you were watching at and sets it as the volume when you open a new video";
+ title: "Remembers the volume of the last video you were watching and sets it when you open a new video";
};
videoHistory: {
label: "Enable video history";
@@ -111,7 +115,6 @@ interface EnUS {
label: "Enable screenshot button";
title: "Adds a button to the player to take a screenshot of the video";
};
- format: { jpeg: "JPEG"; png: "PNG"; webp: "WebP" };
saveAs: { clipboard: "Clipboard"; file: "File" };
selectFormat: { label: "Screenshot format"; title: "The format to save the screenshot in" };
selectSaveAs: { label: "Screenshot save type"; title: "The screenshot save type" };
@@ -122,6 +125,15 @@ interface EnUS {
label: "Enable scroll wheel volume control";
title: "Lets you use the scroll wheel to control the volume of the video you're watching";
};
+ modifierKey: {
+ enable: {
+ label: "Enable modifier key";
+ title: "Press a modifier key to enable volume adjustment with the scroll wheel.";
+ };
+ options: { altKey: "{{KEY}} key"; ctrlKey: "{{KEY}} key"; shiftKey: "{{KEY}} key" };
+ select: { label: "Modifier key"; title: "The modifier key to use" };
+ title: "Scroll Wheel Modifier Key";
+ };
onScreenDisplay: {
colors: {
blue: "Blue";
@@ -142,19 +154,25 @@ interface EnUS {
};
type: { line: "Line"; no_display: "No display"; round: "Round"; text: "Text" };
};
- osdColor: { label: "OSD color"; title: "The color of the On Screen Display" };
+ osdColor: { label: "OSD Color"; title: "Select the color for the On-Screen Display" };
osdHide: {
- label: "Time to hide";
- title: "The amount of milliseconds to wait before hiding the OSD";
+ label: "Hide Delay";
+ title: "Specify the time, in milliseconds, before automatically hiding the OSD";
+ };
+ osdOpacity: {
+ label: "OSD Opacity";
+ title: "Adjust the transparency of the On-Screen Display";
};
- osdOpacity: { label: "OSD opacity"; title: "The opacity of the On Screen Display" };
osdPadding: {
label: "Padding";
- title: "The amount of padding to add to the OSD (in pixels, only applies to corner OSD)";
+ title: "Adjust the spacing around the on-screen display (OSD) in pixels. This applies specifically to corner OSD.";
+ };
+ osdPosition: { label: "OSD Position"; title: "Select the position of the On-Screen Display" };
+ osdType: { label: "OSD Type"; title: "Select the style of On-Screen Display" };
+ osdVolumeAdjustmentSteps: {
+ label: "Volume Change Per Scroll";
+ title: "Adjust the volume change per scroll";
};
- osdPosition: { label: "OSD position"; title: "The position of the On Screen Display" };
- osdType: { label: "OSD type"; title: "The type of On Screen Display" };
- osdVolumeAdjustmentSteps: { label: "Amount to adjust"; title: "The amount to adjust volume per scroll" };
title: "Scroll wheel volume control settings";
};
volumeBoost: {
diff --git a/src/components/Settings/Settings.tsx b/src/components/Settings/Settings.tsx
index 43794a73..d9836e3c 100644
--- a/src/components/Settings/Settings.tsx
+++ b/src/components/Settings/Settings.tsx
@@ -1,12 +1,12 @@
-import type { configuration, configurationKeys } from "@/src/@types";
+import type { ModifierKey, configuration, configurationKeys } from "@/src/types";
import type EnUS from "public/locales/en-US.json";
import type { ChangeEvent, Dispatch, SetStateAction } from "react";
import "@/assets/styles/tailwind.css";
import "@/components/Settings/Settings.css";
import { useNotifications } from "@/hooks";
-import { youtubePlayerSpeedRate } from "@/src/@types";
import { availableLocales, type i18nInstanceType } from "@/src/i18n";
+import { youtubePlayerSpeedRate } from "@/src/types";
import { configurationImportSchema } from "@/src/utils/constants";
import { cn, settingsAreDefault } from "@/src/utils/utilities";
import React, { Suspense, useEffect, useState } from "react";
@@ -52,7 +52,7 @@ function LanguageOptions({
>;
setSelectedDisplayType: Dispatch>;
setSelectedLanguage: Dispatch>;
+ setSelectedModifierKey: Dispatch>;
setSelectedPlayerQuality: Dispatch>;
setSelectedPlayerSpeed: Dispatch>;
setSelectedScreenshotFormat: Dispatch>;
@@ -163,12 +167,31 @@ export default function Settings({
returnObjects: true
});
const {
- format: { jpeg, png, webp },
saveAs: { clipboard, file }
} = t("settings.sections.screenshotButton", {
defaultValue: {},
returnObjects: true
});
+ const scrollWheelVolumeControlModifierKeyOptions = [
+ {
+ label: t("settings.sections.scrollWheelVolumeControl.modifierKey.options.altKey", {
+ KEY: "Alt"
+ }),
+ value: "altKey"
+ },
+ {
+ label: t("settings.sections.scrollWheelVolumeControl.modifierKey.options.ctrlKey", {
+ KEY: "Ctrl"
+ }),
+ value: "ctrlKey"
+ },
+ {
+ label: t("settings.sections.scrollWheelVolumeControl.modifierKey.options.shiftKey", {
+ KEY: "Shift"
+ }),
+ value: "shiftKey"
+ }
+ ] as { label: string; value: ModifierKey }[] as SelectOption[];
const colorOptions: SelectOption[] = [
{
element: ,
@@ -266,9 +289,9 @@ export default function Settings({
].reverse();
const YouTubePlayerSpeedOptions: SelectOption[] = youtubePlayerSpeedRate.map((rate) => ({ label: rate.toString(), value: rate.toString() }));
const ScreenshotFormatOptions: SelectOption[] = [
- { label: png, value: "png" },
- { label: jpeg, value: "jpeg" },
- { label: webp, value: "webp" }
+ { label: "PNG", value: "png" },
+ { label: "JPEG", value: "jpeg" },
+ { label: "WebP", value: "webp" }
];
const ScreenshotSaveAsOptions: SelectOption[] = [
{ label: file, value: "file" },
@@ -406,6 +429,14 @@ export default function Settings({
title={t("settings.sections.miscellaneous.features.hideScrollbar.title")}
type="checkbox"
/>
+
@@ -417,9 +448,28 @@ export default function Settings({
title={t("settings.sections.scrollWheelVolumeControl.enable.title")}
type="checkbox"
/>
+
+
();
const [selectedScreenshotFormat, setSelectedScreenshotFormat] = useState();
const [selectedLanguage, setSelectedLanguage] = useState();
+ const [selectedModifierKey, setSelectedModifierKey] = useState();
const [i18nInstance, setI18nInstance] = useState(null);
useEffect(() => {
const fetchSettings = () => {
@@ -25,15 +26,17 @@ export default function SettingsWrapper(): JSX.Element {
for (const [key, value] of Object.entries(settings)) {
settings[key] = parseStoredValue(value);
}
- setSettings({ ...settings } as configuration);
- setSelectedColor(settings.osd_display_color);
- setSelectedDisplayType(settings.osd_display_type);
- setSelectedDisplayPosition(settings.osd_display_position);
- setSelectedPlayerQuality(settings.player_quality);
- setSelectedPlayerSpeed(settings.player_speed);
- setSelectedScreenshotSaveAs(settings.screenshot_save_as);
- setSelectedScreenshotFormat(settings.screenshot_format);
- setSelectedLanguage(settings.language);
+ const castedSettings = settings as configuration;
+ setSettings({ ...castedSettings });
+ setSelectedColor(castedSettings.osd_display_color);
+ setSelectedDisplayType(castedSettings.osd_display_type);
+ setSelectedDisplayPosition(castedSettings.osd_display_position);
+ setSelectedPlayerQuality(castedSettings.player_quality);
+ setSelectedPlayerSpeed(castedSettings.player_speed.toString());
+ setSelectedScreenshotSaveAs(castedSettings.screenshot_save_as);
+ setSelectedScreenshotFormat(castedSettings.screenshot_format);
+ setSelectedLanguage(castedSettings.language);
+ setSelectedModifierKey(castedSettings.scroll_wheel_volume_control_modifier_key);
});
};
@@ -116,6 +119,7 @@ export default function SettingsWrapper(): JSX.Element {
selectedDisplayPosition={selectedDisplayPosition}
selectedDisplayType={selectedDisplayType}
selectedLanguage={selectedLanguage}
+ selectedModifierKey={selectedModifierKey}
selectedPlayerQuality={selectedPlayerQuality}
selectedPlayerSpeed={selectedPlayerSpeed}
selectedScreenshotFormat={selectedScreenshotFormat}
@@ -124,6 +128,7 @@ export default function SettingsWrapper(): JSX.Element {
setSelectedDisplayPosition={setSelectedDisplayPosition}
setSelectedDisplayType={setSelectedDisplayType}
setSelectedLanguage={setSelectedLanguage}
+ setSelectedModifierKey={setSelectedModifierKey}
setSelectedPlayerQuality={setSelectedPlayerQuality}
setSelectedPlayerSpeed={setSelectedPlayerSpeed}
setSelectedScreenshotFormat={setSelectedScreenshotFormat}
diff --git a/src/features/automaticTheaterMode/index.ts b/src/features/automaticTheaterMode/index.ts
new file mode 100644
index 00000000..e6696125
--- /dev/null
+++ b/src/features/automaticTheaterMode/index.ts
@@ -0,0 +1,33 @@
+import type { YouTubePlayerDiv } from "@/src/types";
+
+import { isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
+
+export async function automaticTheaterMode() {
+ // Wait for the "options" message from the content script
+ const optionsData = await waitForSpecificMessage("options", "request_data", "content");
+ if (!optionsData) return;
+ const {
+ data: { options }
+ } = optionsData;
+ // Extract the necessary properties from the options object
+ const { enable_automatic_theater_mode } = options;
+ // If automatic theater mode isn't enabled return
+ if (!enable_automatic_theater_mode) return;
+ if (!isWatchPage()) return;
+ // Get the player element
+ const playerContainer = isWatchPage() ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null) : null;
+ // If player element is not available, return
+ if (!playerContainer) return;
+ const { width } = await playerContainer.getSize();
+ const {
+ body: { clientWidth }
+ } = document;
+ const isTheaterMode = width === clientWidth;
+ // Get the size button
+ const sizeButton = document.querySelector("button.ytp-size-button") as HTMLButtonElement | null;
+ // If the size button is not available return
+ if (!sizeButton) return;
+ if (!isTheaterMode) {
+ sizeButton.click();
+ }
+}
diff --git a/src/features/featureMenu/index.ts b/src/features/featureMenu/index.ts
index 80294a71..86529ea8 100644
--- a/src/features/featureMenu/index.ts
+++ b/src/features/featureMenu/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
import { createTooltip, isShortsPage, isWatchPage } from "@/src/utils/utilities";
diff --git a/src/features/featureMenu/utils.ts b/src/features/featureMenu/utils.ts
index e528e766..f4a20bf7 100644
--- a/src/features/featureMenu/utils.ts
+++ b/src/features/featureMenu/utils.ts
@@ -1,4 +1,4 @@
-import type { FeatureMenuItemIconId, FeatureMenuItemId, FeatureMenuItemLabelId, WithId } from "@/src/@types";
+import type { FeatureMenuItemIconId, FeatureMenuItemId, FeatureMenuItemLabelId, WithId } from "@/src/types";
import eventManager, { type FeatureName } from "@/src/utils/EventManager";
import { waitForAllElements } from "@/src/utils/utilities";
diff --git a/src/features/loopButton/index.ts b/src/features/loopButton/index.ts
index 390381ac..d8e8093f 100644
--- a/src/features/loopButton/index.ts
+++ b/src/features/loopButton/index.ts
@@ -26,7 +26,7 @@ export async function addLoopButton() {
featureName: "loopButton",
icon: loopSVG,
isToggle: true,
- label: `Loop`,
+ label: window.i18nextInstance.t("pages.content.features.loopButton.label"),
listener: loopButtonClickListener
});
const loopChangedHandler = (mutationList: MutationRecord[]) => {
diff --git a/src/features/maximizePlayerButton/index.ts b/src/features/maximizePlayerButton/index.ts
index 51d7d492..4003bb3a 100644
--- a/src/features/maximizePlayerButton/index.ts
+++ b/src/features/maximizePlayerButton/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
import { waitForSpecificMessage } from "@/src/utils/utilities";
@@ -46,7 +46,7 @@ export async function addMaximizePlayerButton(): Promise {
featureName: "maximizePlayerButton",
icon: maximizeSVG,
isToggle: true,
- label: "Maximize Player",
+ label: window.i18nextInstance.t("pages.content.features.maximizePlayerButton.label"),
listener: maximizePlayerButtonClickListener
});
function ytpLeftButtonMouseEnterListener(event: MouseEvent) {
diff --git a/src/features/maximizePlayerButton/utils.ts b/src/features/maximizePlayerButton/utils.ts
index 03890118..497e5199 100644
--- a/src/features/maximizePlayerButton/utils.ts
+++ b/src/features/maximizePlayerButton/utils.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
let wasInTheatreMode = false;
diff --git a/src/features/playerQuality/index.ts b/src/features/playerQuality/index.ts
index c774fc79..b6a7bf5f 100644
--- a/src/features/playerQuality/index.ts
+++ b/src/features/playerQuality/index.ts
@@ -1,7 +1,6 @@
-import type { YouTubePlayerDiv, YoutubePlayerQualityLabel, YoutubePlayerQualityLevel } from "@/src/@types";
+import type { YouTubePlayerDiv, YoutubePlayerQualityLevel } from "@/src/types";
-import { youtubePlayerQualityLabel, youtubePlayerQualityLevel } from "@/src/@types";
-import { browserColorLog, chooseClosetQuality, isShortsPage, isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
+import { browserColorLog, chooseClosestQuality, isShortsPage, isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
/**
* Sets the player quality based on the options received from a specific message.
@@ -25,9 +24,6 @@ export default async function setPlayerQuality(): Promise {
// If player quality is not specified, return
if (!player_quality) return;
- // Initialize the playerQuality variable
- let playerQuality: YoutubePlayerQualityLabel | YoutubePlayerQualityLevel = player_quality;
-
// Get the player element
const playerContainer = isWatchPage()
? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null)
@@ -45,31 +41,14 @@ export default async function setPlayerQuality(): Promise {
const availableQualityLevels = (await playerContainer.getAvailableQualityLevels()) as YoutubePlayerQualityLevel[];
// Check if the specified player quality is available
- if (playerQuality && playerQuality !== "auto") {
- if (!availableQualityLevels.includes(playerQuality)) {
- // Convert the available quality levels to their corresponding labels
- const availableResolutions = availableQualityLevels.reduce(function (array, elem) {
- if (youtubePlayerQualityLabel[youtubePlayerQualityLevel.indexOf(elem)]) {
- array.push(youtubePlayerQualityLabel[youtubePlayerQualityLevel.indexOf(elem)]);
- }
- return array;
- }, [] as YoutubePlayerQualityLabel[]);
-
- // Choose the closest quality level based on the available resolutions
- playerQuality = chooseClosetQuality(youtubePlayerQualityLabel[youtubePlayerQualityLevel.indexOf(playerQuality)], availableResolutions);
-
- // If the chosen quality level is not available, return
- if (!youtubePlayerQualityLevel.at(youtubePlayerQualityLabel.indexOf(playerQuality))) return;
-
- // Update the playerQuality variable
- playerQuality = youtubePlayerQualityLevel.at(youtubePlayerQualityLabel.indexOf(playerQuality)) as YoutubePlayerQualityLevel;
- }
-
+ if (player_quality && player_quality !== "auto") {
+ const closestQuality = chooseClosestQuality(player_quality, availableQualityLevels);
+ if (!closestQuality) return;
// Log the message indicating the player quality being set
- browserColorLog(`Setting player quality to ${playerQuality}`, "FgMagenta");
+ browserColorLog(`Setting player quality to ${closestQuality}`, "FgMagenta");
// Set the playback quality and update the default quality in the dataset
- playerContainer.setPlaybackQualityRange(playerQuality);
- playerContainer.dataset.defaultQuality = playerQuality;
+ playerContainer.setPlaybackQualityRange(closestQuality);
+ playerContainer.dataset.defaultQuality = closestQuality;
}
}
diff --git a/src/features/playerSpeed/index.ts b/src/features/playerSpeed/index.ts
index 06917dd4..f38f2958 100644
--- a/src/features/playerSpeed/index.ts
+++ b/src/features/playerSpeed/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
import { browserColorLog, isShortsPage, isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
diff --git a/src/features/remainingTime/index.ts b/src/features/remainingTime/index.ts
index 10819a47..c397ffd5 100644
--- a/src/features/remainingTime/index.ts
+++ b/src/features/remainingTime/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
import { isShortsPage, isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
diff --git a/src/features/remainingTime/utils.ts b/src/features/remainingTime/utils.ts
index a692ad05..b255782b 100644
--- a/src/features/remainingTime/utils.ts
+++ b/src/features/remainingTime/utils.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
export function formatTime(timeInSeconds: number) {
timeInSeconds = Math.round(timeInSeconds);
const units: number[] = [
diff --git a/src/features/rememberVolume/index.ts b/src/features/rememberVolume/index.ts
index eff36d67..f4cbe82d 100644
--- a/src/features/rememberVolume/index.ts
+++ b/src/features/rememberVolume/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import { isShortsPage, isWatchPage, waitForSpecificMessage } from "@/src/utils/utilities";
diff --git a/src/features/rememberVolume/utils.ts b/src/features/rememberVolume/utils.ts
index 67cb4178..510ce446 100644
--- a/src/features/rememberVolume/utils.ts
+++ b/src/features/rememberVolume/utils.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv, configuration } from "@/src/@types";
+import type { YouTubePlayerDiv, configuration } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
import { browserColorLog, isShortsPage, isWatchPage, sendContentOnlyMessage, waitForSpecificMessage } from "@/src/utils/utilities";
diff --git a/src/features/screenshotButton/index.ts b/src/features/screenshotButton/index.ts
index ee4ff1db..cc1b1272 100644
--- a/src/features/screenshotButton/index.ts
+++ b/src/features/screenshotButton/index.ts
@@ -1,7 +1,7 @@
import eventManager from "@/src/utils/EventManager";
import { waitForSpecificMessage } from "@/src/utils/utilities";
-import { addFeatureItemToMenu, removeFeatureItemFromMenu } from "../featureMenu/utils";
+import { addFeatureItemToMenu, getFeatureMenuItem, removeFeatureItemFromMenu } from "../featureMenu/utils";
async function takeScreenshot(videoElement: HTMLVideoElement) {
try {
@@ -35,13 +35,26 @@ async function takeScreenshot(videoElement: HTMLVideoElement) {
switch (screenshot_save_as) {
case "clipboard": {
- const screenshotTooltip = document.querySelector("div#yte-screenshot-tooltip");
- if (screenshotTooltip) {
- const clipboardImage = new ClipboardItem({ "image/png": blob });
- navigator.clipboard.write([clipboardImage]);
- navigator.clipboard.writeText(dataUrl);
- screenshotTooltip.textContent = window.i18nextInstance.t("pages.content.features.screenshotButton.copiedToClipboard");
- }
+ const tooltip = document.createElement("div");
+ const screenshotMenuItem = getFeatureMenuItem("screenshotButton");
+ if (!screenshotMenuItem) return;
+ const rect = screenshotMenuItem.getBoundingClientRect();
+ tooltip.classList.add("yte-button-tooltip");
+ tooltip.classList.add("ytp-tooltip");
+ tooltip.classList.add("ytp-rounded-tooltip");
+ tooltip.classList.add("ytp-bottom");
+ tooltip.id = "yte-screenshot-tooltip";
+ tooltip.style.left = `${rect.left + rect.width / 2}px`;
+ tooltip.style.top = `${rect.top - 2}px`;
+ tooltip.style.zIndex = "99999";
+ tooltip.textContent = window.i18nextInstance.t("pages.content.features.screenshotButton.copiedToClipboard");
+ document.body.appendChild(tooltip);
+ const clipboardImage = new ClipboardItem({ "image/png": blob });
+ navigator.clipboard.write([clipboardImage]);
+ navigator.clipboard.writeText(dataUrl);
+ setTimeout(() => {
+ tooltip.remove();
+ }, 1200);
break;
}
case "file": {
diff --git a/src/features/scrollWheelVolumeControl/index.ts b/src/features/scrollWheelVolumeControl/index.ts
index 364c1f71..e30c03e6 100644
--- a/src/features/scrollWheelVolumeControl/index.ts
+++ b/src/features/scrollWheelVolumeControl/index.ts
@@ -1,9 +1,14 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import { isShortsPage, isWatchPage, waitForAllElements, waitForSpecificMessage } from "@/src/utils/utilities";
-import { adjustVolume, drawVolumeDisplay, getScrollDirection, setupScrollListeners } from "./utils";
+import { adjustVolume, drawVolumeDisplay, setupScrollListeners } from "./utils";
+function preventScroll(event: Event) {
+ event.preventDefault();
+ event.stopImmediatePropagation();
+ event.stopPropagation();
+}
/**
* Adjusts the volume on scroll wheel events.
* It listens for scroll wheel events on specified container selectors,
@@ -28,10 +33,26 @@ export default async function adjustVolumeOnScrollWheel(): Promise {
// Define the event handler for the scroll wheel events
const handleWheel = async (event: Event) => {
- const wheelEvent = event as WheelEvent | undefined;
-
- // If it's not a wheel event, return
- if (!wheelEvent) return;
+ preventScroll(event);
+ // Wait for the "options" message from the content script
+ const optionsData = await waitForSpecificMessage("options", "request_data", "content");
+ if (!optionsData) return;
+ const {
+ data: { options }
+ } = optionsData;
+ // Extract the necessary properties from the options object
+ const { enable_scroll_wheel_volume_control, enable_scroll_wheel_volume_control_modifier_key, scroll_wheel_volume_control_modifier_key } = options;
+ const wheelEvent = event as WheelEvent;
+ if (
+ !(
+ (enable_scroll_wheel_volume_control &&
+ enable_scroll_wheel_volume_control_modifier_key &&
+ scroll_wheel_volume_control_modifier_key &&
+ wheelEvent[scroll_wheel_volume_control_modifier_key]) ||
+ (enable_scroll_wheel_volume_control && !enable_scroll_wheel_volume_control_modifier_key)
+ )
+ )
+ return;
// Get the player element
const playerContainer = isWatchPage()
? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null)
@@ -42,15 +63,9 @@ export default async function adjustVolumeOnScrollWheel(): Promise {
if (!playerContainer) return;
// Adjust the volume based on the scroll direction
- const scrollDelta = getScrollDirection(wheelEvent.deltaY);
- // Wait for the "options" message from the content script
- const optionsData = await waitForSpecificMessage("options", "request_data", "content");
- if (!optionsData) return;
- const {
- data: { options }
- } = optionsData;
+ const scrollDelta = wheelEvent.deltaY < 0 ? 1 : -1;
// Adjust the volume based on the scroll direction and options
- const { newVolume } = await adjustVolume(scrollDelta, options.volume_adjustment_steps);
+ const { newVolume } = await adjustVolume(playerContainer, scrollDelta, options.volume_adjustment_steps);
// Update the volume display
drawVolumeDisplay({
@@ -60,7 +75,7 @@ export default async function adjustVolumeOnScrollWheel(): Promise {
displayPadding: options.osd_display_padding,
displayPosition: options.osd_display_position,
displayType: options.osd_display_type,
- playerContainer: playerContainer || null,
+ playerContainer: playerContainer,
volume: newVolume
});
};
diff --git a/src/features/scrollWheelVolumeControl/utils.ts b/src/features/scrollWheelVolumeControl/utils.ts
index 8d66d197..bab674f2 100644
--- a/src/features/scrollWheelVolumeControl/utils.ts
+++ b/src/features/scrollWheelVolumeControl/utils.ts
@@ -1,33 +1,23 @@
-import type { OnScreenDisplayColor, OnScreenDisplayPosition, OnScreenDisplayType, Selector, YouTubePlayerDiv } from "@/src/@types";
+import type { OnScreenDisplayColor, OnScreenDisplayPosition, OnScreenDisplayType, Selector, YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/src/utils/EventManager";
-import { browserColorLog, clamp, isShortsPage, isWatchPage, round, toDivisible } from "@/src/utils/utilities";
+import { browserColorLog, clamp, createStyledElement, isShortsPage, round, toDivisible } from "@/src/utils/utilities";
-/**
- * Get the scroll direction based on the deltaY value.
- *
- * @param deltaY - The deltaY value from the scroll event.
- * @returns The scroll direction: 1 for scrolling up, -1 for scrolling down.
- */
-export function getScrollDirection(deltaY: number): number {
- return deltaY < 0 ? 1 : -1;
-}
/**
* Adjust the volume based on the scroll direction.
*
+ * @param playerContainer - The player container element.
* @param scrollDelta - The scroll direction.
* @param adjustmentSteps - The volume adjustment steps.
* @returns Promise that resolves with the new volume.
*/
-export function adjustVolume(scrollDelta: number, volumeStep: number): Promise<{ newVolume: number; oldVolume: number }> {
+export function adjustVolume(
+ playerContainer: YouTubePlayerDiv,
+ scrollDelta: number,
+ volumeStep: number
+): Promise<{ newVolume: number; oldVolume: number }> {
return new Promise((resolve) => {
(async () => {
- const playerContainer = isWatchPage()
- ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null)
- : isShortsPage()
- ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null)
- : null;
- if (!playerContainer) return;
if (!playerContainer.getVolume) return;
if (!playerContainer.setVolume) return;
if (!playerContainer.isMuted) return;
@@ -53,16 +43,35 @@ export function setupScrollListeners(selector: Selector, handleWheel: (event: Ev
const elements: NodeListOf = document.querySelectorAll(selector);
if (!elements.length) return browserColorLog(`No elements found with selector ${selector}`, "FgRed");
for (const element of elements) {
- const mouseWheelListener = (e: Event) => {
- if (element.clientHeight === 0) return;
- e.preventDefault();
- e.stopPropagation();
- e.stopImmediatePropagation();
- handleWheel(e);
- };
- eventManager.addEventListener(element, "wheel", mouseWheelListener, "scrollWheelVolumeControl");
+ eventManager.addEventListener(element, "wheel", handleWheel, "scrollWheelVolumeControl", { passive: false });
}
}
+function calculateCanvasPosition(displayPosition: OnScreenDisplayPosition, displayPadding: number, paddingTop: number, paddingBottom: number) {
+ let styles: Partial = {};
+
+ switch (displayPosition) {
+ case "top_left":
+ styles = { left: `${displayPadding}px`, top: `${displayPadding + paddingTop}px` };
+ break;
+ case "top_right":
+ styles = { right: `${displayPadding}px`, top: `${displayPadding + paddingTop}px` };
+ break;
+ case "bottom_left":
+ styles = { bottom: `${displayPadding + paddingBottom}px`, left: `${displayPadding}px` };
+ break;
+ case "bottom_right":
+ styles = { bottom: `${displayPadding + paddingBottom}px`, right: `${displayPadding}px` };
+ break;
+ case "center":
+ styles = { left: "50%", top: "50%", transform: "translate(-50%, -50%)" };
+ break;
+ default:
+ console.error("Invalid display position");
+ break;
+ }
+
+ return styles;
+}
/**
* Draw the volume display over the player.
*
@@ -88,15 +97,6 @@ export function drawVolumeDisplay({
volume: number;
}) {
volume = clamp(volume, 0, 100);
- const canvas = document.createElement("canvas");
- canvas.id = "volume-display";
- const context = canvas.getContext("2d");
-
- if (!context) {
- browserColorLog("Canvas not supported", "FgRed");
- return;
- }
-
// Set canvas dimensions based on player/container dimensions
if (!playerContainer) {
browserColorLog("Player container not found", "FgRed");
@@ -129,100 +129,77 @@ export function drawVolumeDisplay({
browserColorLog(`Clamped display padding to ${displayPadding}`, "FgRed");
}
- // Set canvas styles for positioning
- canvas.style.position = "absolute";
+ const bottomElement: HTMLDivElement | null =
+ document.querySelector(
+ "ytd-reel-video-renderer[is-active] > div.overlay.ytd-reel-video-renderer > ytd-reel-player-overlay-renderer > div > ytd-reel-player-header-renderer"
+ ) ?? document.querySelector(".ytp-chrome-bottom");
+ const { top: topRectTop = 0 } = document.querySelector(".player-controls > ytd-shorts-player-controls")?.getBoundingClientRect() || {};
+ const { bottom: bottomRectBottom = 0, top: bottomRectTop = 0 } = bottomElement?.getBoundingClientRect() || {};
+ const heightExcludingMarginPadding = bottomElement
+ ? bottomElement.offsetHeight -
+ (parseInt(getComputedStyle(bottomElement).marginTop, 10) +
+ parseInt(getComputedStyle(bottomElement).marginBottom, 10) +
+ parseInt(getComputedStyle(bottomElement).paddingTop, 10) +
+ parseInt(getComputedStyle(bottomElement).paddingBottom, 10)) +
+ 10
+ : 0;
+ const paddingTop = isShortsPage() ? topRectTop / 2 : 0;
+ const paddingBottom = isShortsPage() ? heightExcludingMarginPadding : Math.round(bottomRectBottom - bottomRectTop);
- switch (displayPosition) {
- case "top_left":
- canvas.style.top = `${displayPadding}px`;
- canvas.style.left = `${displayPadding}px`;
- break;
- case "top_right":
- canvas.style.top = `${displayPadding}px`;
- canvas.style.right = `${displayPadding}px`;
- break;
- case "bottom_left":
- canvas.style.bottom = `${displayPadding}px`;
- canvas.style.left = `${displayPadding}px`;
- break;
- case "bottom_right":
- canvas.style.bottom = `${displayPadding}px`;
- canvas.style.right = `${displayPadding}px`;
- break;
- case "center":
- canvas.style.top = "50%";
- canvas.style.left = "50%";
- canvas.style.transform = "translate(-50%, -50%)";
- break;
- default:
- console.error("Invalid display position");
- return;
- }
- switch (displayType) {
- case "text": {
- const fontSize = Math.min(originalWidth, originalHeight) / 10;
- context.font = `${clamp(fontSize, 48, 72)}px bold Arial`;
- const { width: textWidth } = context.measureText(`${round(volume)}`);
- width = textWidth + 4;
- height = clamp(fontSize, 48, 72) + 4;
- break;
- }
- case "line": {
- const maxLineWidth = 100; // Maximum width of the volume line
- const lineHeight = 5; // Height of the volume line
- const lineWidth = Math.round(round(volume / 100, 2) * maxLineWidth);
- width = lineWidth;
- height = lineHeight;
- break;
- }
- case "round": {
- const lineWidth = 5;
- const radius = Math.min(width, height, 75) / 2 - lineWidth; // Maximum radius based on canvas dimensions
- const circleWidth = radius * 2 + lineWidth * 2;
- width = circleWidth;
- height = circleWidth;
- break;
- }
- default:
- console.error("Invalid display type");
- return;
- }
+ const canvas = createStyledElement("volume-display", "canvas", {
+ pointerEvents: "none",
+ position: "absolute",
+ zIndex: "2021",
+ ...calculateCanvasPosition(displayPosition, displayPadding, paddingTop, paddingBottom)
+ });
+ const context = canvas.getContext("2d");
- // Apply content dimensions to the canvas
- canvas.width = width;
- canvas.height = height;
- canvas.style.zIndex = "2021";
- canvas.style.pointerEvents = "none";
+ if (!context) {
+ browserColorLog("Canvas not supported", "FgRed");
+ return;
+ }
- // Clear canvas
- context.clearRect(0, 0, width, height);
- context.fillStyle = displayColor;
- context.globalAlpha = displayOpacity / 100;
- // Draw volume representation based on display type
switch (displayType) {
case "text": {
- const fontSize = Math.min(originalWidth, originalHeight) / 10;
- context.font = `${clamp(fontSize, 48, 72)}px bold Arial`;
+ const fontSize = clamp(Math.min(originalWidth, originalHeight) / 10, 48, 72);
+ canvas.width = fontSize + 4;
+ canvas.height = fontSize + 4;
+ // Clear canvas
+ context.clearRect(0, 0, canvas.width, canvas.height);
context.textAlign = "center";
context.textBaseline = "middle";
- context.fillText(`${round(volume)}`, width / 2, height / 2);
+ context.fillStyle = displayColor;
+ context.globalAlpha = displayOpacity / 100;
+ context.font = `${fontSize}px bold Arial`;
+ context.fillText(`${round(volume)}`, canvas.width / 2, canvas.height / 2);
break;
}
case "line": {
const maxLineWidth = 100; // Maximum width of the volume line
const lineHeight = 5; // Height of the volume line
const lineWidth = Math.round(round(volume / 100, 2) * maxLineWidth);
- const lineX = (width - lineWidth) / 2;
- const lineY = (height - lineHeight) / 2;
+ canvas.width = lineWidth;
+ canvas.height = lineHeight;
+ // Clear canvas
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ const lineX = (canvas.width - lineWidth) / 2;
+ const lineY = (canvas.height - lineHeight) / 2;
+ context.fillStyle = displayColor;
+ context.globalAlpha = displayOpacity / 100;
context.fillRect(lineX, lineY, lineWidth, lineHeight);
break;
}
case "round": {
const lineWidth = 5;
- const centerX = width / 2;
- const centerY = height / 2;
const radius = Math.min(width, height, 75) / 2 - lineWidth; // Maximum radius based on canvas dimensions
+ const circleWidth = radius * 2 + lineWidth * 2;
+ canvas.width = circleWidth;
+ canvas.height = circleWidth;
+ // Clear canvas
+ context.clearRect(0, 0, canvas.width, canvas.height);
+ const centerX = canvas.width / 2;
+ const centerY = canvas.height / 2;
const startAngle = Math.PI + Math.PI * round(volume / 100, 2); // Start angle based on volume
const endAngle = Math.PI - Math.PI * round(volume / 100, 2); // End angle based on volume
// Draw the volume arc as a circle at 100% volume
@@ -236,7 +213,7 @@ export function drawVolumeDisplay({
}
default:
console.error("Invalid display type");
- break;
+ return;
}
// Append canvas to player container if it doesn't already exist
@@ -250,33 +227,4 @@ export function drawVolumeDisplay({
setTimeout(() => {
canvas.remove();
}, displayHideTime);
- const topElement = document.querySelector(".player-controls > ytd-shorts-player-controls");
- const bottomElement: HTMLDivElement | null =
- document.querySelector(
- "ytd-reel-video-renderer[is-active] > div.overlay.ytd-reel-video-renderer > ytd-reel-player-overlay-renderer > div > ytd-reel-player-header-renderer"
- ) ?? document.querySelector(".ytp-chrome-bottom");
- const topRect = topElement?.getBoundingClientRect();
- const bottomRect = bottomElement?.getBoundingClientRect();
- const heightExcludingMarginPadding = bottomElement
- ? bottomElement.offsetHeight -
- (parseInt(getComputedStyle(bottomElement).marginTop, 10) +
- parseInt(getComputedStyle(bottomElement).marginBottom, 10) +
- parseInt(getComputedStyle(bottomElement).paddingTop, 10) +
- parseInt(getComputedStyle(bottomElement).paddingBottom, 10)) +
- 10
- : 0;
- const paddingTop = topRect ? (isShortsPage() ? topRect.top / 2 : 0) : 0;
- const paddingBottom = bottomRect ? (isShortsPage() ? heightExcludingMarginPadding : Math.round(bottomRect.bottom - bottomRect.top)) : 0;
- switch (displayPosition) {
- case "top_left":
- case "top_right":
- canvas.style.top = `${displayPadding + paddingTop}px`;
- break;
- case "bottom_left":
- case "bottom_right":
- canvas.style.bottom = `${displayPadding + paddingBottom}px`;
- break;
- default:
- return;
- }
}
diff --git a/src/features/videoHistory/index.ts b/src/features/videoHistory/index.ts
index a515dc46..11e697c0 100644
--- a/src/features/videoHistory/index.ts
+++ b/src/features/videoHistory/index.ts
@@ -1,10 +1,17 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { VideoHistoryEntry, YouTubePlayerDiv } from "@/src/types";
import eventManager from "@/utils/EventManager";
-import { browserColorLog, createTooltip, isShortsPage, isWatchPage, sendContentMessage, waitForSpecificMessage } from "@/utils/utilities";
+import {
+ browserColorLog,
+ createStyledElement,
+ createTooltip,
+ isShortsPage,
+ isWatchPage,
+ sendContentMessage,
+ waitForSpecificMessage
+} from "@/utils/utilities";
import { formatTime } from "../remainingTime/utils";
-
export async function setupVideoHistory() {
// Wait for the "options" message from the content script
const optionsData = await waitForSpecificMessage("options", "request_data", "content");
@@ -23,7 +30,7 @@ export async function setupVideoHistory() {
if (playerVideoData.isLive) return;
const { video_id: videoId } = await playerContainer.getVideoData();
if (!videoId) return;
- const videoElement = document.querySelector("video.video-stream.html5-main-video") as HTMLVideoElement | null;
+ const videoElement = playerContainer.querySelector("video.video-stream.html5-main-video") as HTMLVideoElement | null;
if (!videoElement) return;
const videoPlayerTimeUpdateListener = async () => {
@@ -63,126 +70,146 @@ export async function promptUserToResumeVideo() {
data: { video_history_entry }
} = videoHistoryOneData;
if (video_history_entry && video_history_entry.status === "watching" && video_history_entry.timestamp > 0) {
- // Check if the prompt element already exists
- const prompt = document.getElementById("resume-prompt") ?? document.createElement("div");
- // Check if the prompt progress bar already exists
- const progressBar = document.getElementById("resume-prompt-progress-bar") ?? document.createElement("div");
- const progressBarDuration = 15;
- // Create a countdown timer
- let countdown = 15; // Countdown in seconds
- const countdownInterval = setInterval(() => {
+ createResumePrompt(video_history_entry, playerContainer);
+ }
+}
+// Utility function to check if an element exists
+const elementExists = (elementId: string) => !!document.getElementById(elementId);
+function createResumePrompt(videoHistoryEntry: VideoHistoryEntry, playerContainer: YouTubePlayerDiv) {
+ const progressBarId = "resume-prompt-progress-bar";
+ const overlayId = "resume-prompt-overlay";
+ const closeButtonId = "resume-prompt-close-button";
+ const resumeButtonId = "resume-prompt-button";
+ const promptId = "resume-prompt";
+ const progressBarDuration = 15;
+ let countdownInterval: NodeJS.Timeout | undefined;
+
+ const prompt = createStyledElement(promptId, "div", {
+ backgroundColor: "#181a1b",
+ borderRadius: "5px",
+ bottom: "10px",
+ boxShadow: "0px 0px 10px rgba(0, 0, 0, 0.2)",
+ left: "10px",
+ padding: "12px",
+ paddingBottom: "17px",
+ position: "fixed",
+ transition: "all 0.5s ease-in-out",
+ zIndex: "25000"
+ });
+ const progressBar = createStyledElement(progressBarId, "div", {
+ backgroundColor: "#007acc",
+ borderBottomLeftRadius: "5px",
+ borderBottomRightRadius: "5px",
+ bottom: "0",
+ height: "5px",
+ left: "0",
+ position: "absolute",
+ transition: "all 0.5s ease-in-out",
+ width: "100%",
+ zIndex: "1000"
+ });
+
+ const overlay = createStyledElement(overlayId, "div", {
+ backgroundColor: "rgba(0, 0, 0, 0.75)",
+ cursor: "pointer",
+ height: "100%",
+ left: "0",
+ position: "fixed",
+ top: "0",
+ width: "100%",
+ zIndex: "2500"
+ });
+
+ const closeButton = createStyledElement(closeButtonId, "button", {
+ backgroundColor: "transparent",
+ border: "0",
+ color: "#fff",
+ cursor: "pointer",
+ fontSize: "16px",
+ lineHeight: "1px",
+ padding: "5px",
+ position: "absolute",
+ right: "-2px",
+ top: "2px"
+ });
+ closeButton.textContent = "ₓ";
+
+ const resumeButton = createStyledElement(resumeButtonId, "button", {
+ backgroundColor: "hsl(213, 80%, 50%)",
+ border: "transparent",
+ borderRadius: "5px",
+ boxShadow: "0px 0px 5px rgba(0, 0, 0, 0.2)",
+ color: "white",
+ cursor: "pointer",
+ padding: "5px",
+ textAlign: "center",
+ transition: "all 0.5s ease-in-out",
+ verticalAlign: "middle"
+ });
+ resumeButton.textContent = window.i18nextInstance.t("pages.content.features.videoHistory.resumeButton");
+
+ function startCountdown() {
+ if (prompt) prompt.style.display = "block";
+ if (overlay) overlay.style.display = "block";
+ let countdown = progressBarDuration;
+ countdownInterval = setInterval(() => {
countdown--;
- progressBar.style.width = `${(countdown / progressBarDuration) * 100}%`; // Update the progress bar
+ progressBar.style.width = `${(countdown / progressBarDuration) * 100}%`;
if (countdown <= 0) {
- // Automatically hide the prompt when the countdown reaches 0
- clearInterval(countdownInterval);
- prompt.style.display = "none";
- overlay.style.display = "none";
+ hidePrompt();
}
}, 1000);
- if (!document.getElementById("resume-prompt-progress-bar")) {
- progressBar.id = "resume-prompt-progress-bar";
- progressBar.style.width = "100%";
- progressBar.style.height = "5px"; // Height of the progress bar
- progressBar.style.backgroundColor = "#007acc"; // Progress bar color
- progressBar.style.position = "absolute";
- progressBar.style.zIndex = "1000";
- progressBar.style.left = "0"; // Place at the left of the prompt
- progressBar.style.bottom = "0"; // Place at the bottom of the prompt
- progressBar.style.transition = "all 0.5s ease-in-out";
- progressBar.style.borderBottomRightRadius = "5px";
- progressBar.style.borderBottomLeftRadius = "5px";
- prompt.appendChild(progressBar);
- }
- const resumeButtonClickListener = () => {
- // Hide the prompt and clear the countdown timer
- clearInterval(countdownInterval);
- prompt.style.display = "none";
- overlay.style.display = "none";
- browserColorLog(window.i18nextInstance.t("messages.resumingVideo", { VIDEO_TIME: formatTime(video_history_entry.timestamp) }), "FgGreen");
- playerContainer.seekTo(video_history_entry.timestamp, true);
- };
- const overlay = document.getElementById("resume-prompt-overlay") ?? document.createElement("div");
- const resumeButton = document.getElementById("resume-prompt-button") ?? document.createElement("button");
- const closeButton = document.getElementById("resume-prompt-close-button") ?? document.createElement("button");
-
- // Create the overlay if it doesn't exist
- if (!document.getElementById("resume-prompt-overlay")) {
- overlay.style.position = "fixed";
- overlay.style.top = "0";
- overlay.style.left = "0";
- overlay.style.width = "100%";
- overlay.style.height = "100%";
- overlay.style.backgroundColor = "rgba(0, 0, 0, 0.75)";
- overlay.style.zIndex = "2500";
- overlay.style.cursor = "pointer";
- document.body.appendChild(overlay);
- }
- // Create the close button if it doesn't exist
- if (!document.getElementById("resume-prompt-close-button")) {
- closeButton.id = "resume-prompt-close-button";
- closeButton.textContent = "ₓ";
- closeButton.style.fontSize = "16px";
- closeButton.style.position = "absolute";
- closeButton.style.top = "2px";
- closeButton.style.right = "-2px";
- closeButton.style.backgroundColor = "transparent";
- closeButton.style.color = "#fff";
- closeButton.style.border = "0";
- closeButton.style.padding = "5px";
- closeButton.style.cursor = "pointer";
- closeButton.style.lineHeight = "1px";
- closeButton.dataset.title = window.i18nextInstance.t("pages.content.features.videoHistory.resumePrompt.close");
- const { listener: resumePromptCloseButtonMouseOverListener } = createTooltip({
- element: closeButton,
- featureName: "videoHistory",
- id: "yte-resume-prompt-close-button-tooltip"
- });
- eventManager.addEventListener(closeButton, "mouseover", resumePromptCloseButtonMouseOverListener, "videoHistory");
- prompt.appendChild(closeButton);
- }
- // Create the prompt element if it doesn't exist
- if (!document.getElementById("resume-prompt")) {
- prompt.id = "resume-prompt";
- prompt.style.position = "fixed";
- prompt.style.bottom = "10px";
- prompt.style.left = "10px";
- prompt.style.backgroundColor = "#181a1b";
- prompt.style.padding = "12px";
- prompt.style.paddingBottom = "17px";
- prompt.style.transition = "all 0.5s ease-in-out";
- prompt.style.borderRadius = "5px";
- prompt.style.boxShadow = "0px 0px 10px rgba(0, 0, 0, 0.2)";
- prompt.style.zIndex = "25000";
- resumeButton.id = "resume-prompt-button";
- resumeButton.textContent = window.i18nextInstance.t("pages.content.features.videoHistory.resumeButton");
- resumeButton.style.backgroundColor = "hsl(213, 80%, 50%)";
- resumeButton.style.border = "transparent";
- resumeButton.style.color = "white";
- resumeButton.style.padding = "5px";
- resumeButton.style.borderRadius = "5px";
- resumeButton.style.boxShadow = "0px 0px 5px rgba(0, 0, 0, 0.2)";
- resumeButton.style.cursor = "pointer";
- resumeButton.style.textAlign = "center";
- resumeButton.style.verticalAlign = "middle";
- resumeButton.style.transition = "all 0.5s ease-in-out";
-
- prompt.appendChild(resumeButton);
- document.body.appendChild(prompt);
- }
- if (document.getElementById("resume-prompt-button")) {
- eventManager.removeEventListener(resumeButton, "click", "videoHistory");
- }
- const closeListener = () => {
- clearInterval(countdownInterval);
- prompt.style.display = "none";
- overlay.style.display = "none";
- };
- eventManager.addEventListener(resumeButton, "click", resumeButtonClickListener, "videoHistory");
- eventManager.addEventListener(overlay, "click", closeListener, "videoHistory");
- eventManager.addEventListener(closeButton, "click", closeListener, "videoHistory");
- // Display the prompt
- prompt.style.display = "block";
+ }
+
+ function hidePrompt() {
+ clearInterval(countdownInterval);
+ prompt.style.display = "none";
+ overlay.style.display = "none";
+ }
+
+ function resumeButtonClickListener() {
+ hidePrompt();
+ browserColorLog(window.i18nextInstance.t("messages.resumingVideo", { VIDEO_TIME: formatTime(videoHistoryEntry.timestamp) }), "FgGreen");
+ playerContainer.seekTo(videoHistoryEntry.timestamp, true);
+ }
+
+ if (!elementExists(progressBarId)) {
+ prompt.appendChild(progressBar);
+ }
+
+ if (!elementExists(overlayId)) {
+ document.body.appendChild(overlay);
+ }
+
+ if (!elementExists(closeButtonId)) {
+ const { listener: resumePromptCloseButtonMouseOverListener } = createTooltip({
+ element: closeButton,
+ featureName: "videoHistory",
+ id: "yte-resume-prompt-close-button-tooltip",
+ text: window.i18nextInstance.t("pages.content.features.videoHistory.resumePrompt.close")
+ });
+ eventManager.addEventListener(closeButton, "mouseover", resumePromptCloseButtonMouseOverListener, "videoHistory");
+ prompt.appendChild(closeButton);
+ }
+
+ startCountdown();
+
+ if (elementExists(resumeButtonId)) {
+ eventManager.removeEventListener(resumeButton, "click", "videoHistory");
+ }
+
+ const closeListener = () => {
+ hidePrompt();
+ };
+
+ eventManager.addEventListener(resumeButton, "click", resumeButtonClickListener, "videoHistory");
+ eventManager.addEventListener(overlay, "click", closeListener, "videoHistory");
+ eventManager.addEventListener(closeButton, "click", closeListener, "videoHistory");
+
+ // Display the prompt
+ if (!elementExists(promptId)) {
+ document.body.appendChild(prompt);
+ prompt.appendChild(resumeButton);
}
}
diff --git a/src/features/videoHistory/utils.ts b/src/features/videoHistory/utils.ts
index b36b93ca..5e54a760 100644
--- a/src/features/videoHistory/utils.ts
+++ b/src/features/videoHistory/utils.ts
@@ -1,4 +1,4 @@
-import type { VideoHistoryStatus, VideoHistoryStorage } from "@/src/@types";
+import type { VideoHistoryStatus, VideoHistoryStorage } from "@/src/types";
export function getVideoHistory() {
return JSON.parse(window.localStorage.getItem("videoHistory") ?? "{}") as VideoHistoryStorage;
}
diff --git a/src/features/volumeBoost/index.ts b/src/features/volumeBoost/index.ts
index 1bac393f..d632d8fd 100644
--- a/src/features/volumeBoost/index.ts
+++ b/src/features/volumeBoost/index.ts
@@ -1,4 +1,4 @@
-import type { YouTubePlayerDiv } from "@/src/@types";
+import type { YouTubePlayerDiv } from "@/src/types";
import { browserColorLog, formatError, waitForSpecificMessage } from "@/src/utils/utilities";
diff --git a/src/hooks/useNotifications/context.ts b/src/hooks/useNotifications/context.ts
index a999ce9a..5661122a 100644
--- a/src/hooks/useNotifications/context.ts
+++ b/src/hooks/useNotifications/context.ts
@@ -1,4 +1,4 @@
-import type { Notification, NotificationAction, NotificationType } from "@/src/@types";
+import type { Notification, NotificationAction, NotificationType } from "@/src/types";
import { createContext } from "react";
diff --git a/src/hooks/useNotifications/provider.tsx b/src/hooks/useNotifications/provider.tsx
index a8fb22f7..178c26be 100644
--- a/src/hooks/useNotifications/provider.tsx
+++ b/src/hooks/useNotifications/provider.tsx
@@ -1,4 +1,4 @@
-import type { Notification, NotificationAction, NotificationType } from "@/src/@types";
+import type { Notification, NotificationAction, NotificationType } from "@/src/types";
import { isNotStrictEqual } from "@/src/utils/utilities";
import React, { type ReactElement, useEffect, useState } from "react";
diff --git a/src/i18n/index.ts b/src/i18n/index.ts
index 11b90a43..5b470198 100644
--- a/src/i18n/index.ts
+++ b/src/i18n/index.ts
@@ -18,8 +18,10 @@ export async function i18nService(locale: AvailableLocales) {
extensionURL = chrome.runtime.getURL("");
}
if (!availableLocales.includes(locale)) throw new Error(`The locale '${locale}' is not available`);
- const response = await fetch(`${extensionURL}locales/${locale}.json`).catch((err) => console.error(err));
- const translations = (await response?.json()) as typeof import("../../public/locales/en-US.json");
+ const response = await fetch(`${extensionURL}locales/${locale}.json`).catch((err) => {
+ throw new Error(err);
+ });
+ const translations = (await response.json()) as typeof import("../../public/locales/en-US.json");
const i18nextInstance = await new Promise((resolve, reject) => {
const resources: {
[k in AvailableLocales]?: {
diff --git a/src/pages/content/index.tsx b/src/pages/content/index.tsx
index 9fb629cc..8fbb7e1c 100644
--- a/src/pages/content/index.tsx
+++ b/src/pages/content/index.tsx
@@ -1,5 +1,6 @@
-import type { ExtensionSendOnlyMessageMappings, Messages, YouTubePlayerDiv } from "@/src/@types";
+import type { ExtensionSendOnlyMessageMappings, Messages, YouTubePlayerDiv } from "@/src/types";
+import { automaticTheaterMode } from "@/src/features/automaticTheaterMode";
import { enableFeatureMenu } from "@/src/features/featureMenu";
import { updateFeatureMenuItemLabel, updateFeatureMenuTitle } from "@/src/features/featureMenu/utils";
import { enableHideScrollBar } from "@/src/features/hideScrollBar";
@@ -17,9 +18,26 @@ import { promptUserToResumeVideo, setupVideoHistory } from "@/src/features/video
import volumeBoost from "@/src/features/volumeBoost";
import { i18nService } from "@/src/i18n";
import eventManager from "@/utils/EventManager";
-import { browserColorLog, formatError, sendContentOnlyMessage, waitForSpecificMessage } from "@/utils/utilities";
+import {
+ browserColorLog,
+ formatError,
+ isShortsPage,
+ isWatchPage,
+ sendContentOnlyMessage,
+ waitForAllElements,
+ waitForSpecificMessage
+} from "@/utils/utilities";
// TODO: Add always show progressbar feature
+/**
+ * Creates a hidden div element with a specific ID that can be used to receive messages from YouTube.
+ * The element is appended to the document's root element.
+ */
+const element = document.createElement("div");
+element.style.display = "none";
+element.id = "yte-message-from-youtube";
+document.documentElement.appendChild(element);
+
// eslint-disable-next-line @typescript-eslint/no-unused-vars
const alwaysShowProgressBar = async function () {
const player = document.querySelector("#movie_player") as YouTubePlayerDiv | null;
@@ -59,20 +77,13 @@ const alwaysShowProgressBar = async function () {
progressLoad += progressWidth;
};
-/**
- * Creates a hidden div element with a specific ID that can be used to receive messages from YouTube.
- * The element is appended to the document's root element.
- */
-const element = document.createElement("div");
-element.style.display = "none";
-element.id = "yte-message-from-youtube";
-document.documentElement.appendChild(element);
-
-window.onload = async function () {
+window.addEventListener("DOMContentLoaded", async function () {
enableRememberVolume();
enableHideScrollBar();
- const enableFeatures = () => {
+ const enableFeatures = async () => {
+ // Wait for the specified container selectors to be available on the page
+ await waitForAllElements(["div#player", "div#player-wide-container", "div#video-container", "div#player-container"]);
eventManager.removeAllEventListeners(["featureMenu"]);
enableFeatureMenu();
addLoopButton();
@@ -87,6 +98,7 @@ window.onload = async function () {
setupVideoHistory();
promptUserToResumeVideo();
setupRemainingTime();
+ automaticTheaterMode();
};
const response = await waitForSpecificMessage("language", "request_data", "content");
if (!response) return;
@@ -95,6 +107,7 @@ window.onload = async function () {
} = response;
const i18nextInstance = await i18nService(language);
window.i18nextInstance = i18nextInstance;
+ if (isWatchPage() || isShortsPage()) document.addEventListener("yt-navigate-finish", enableFeatures);
document.addEventListener("yt-player-updated", enableFeatures);
/**
* Listens for the "yte-message-from-youtube" event and handles incoming messages from the YouTube page.
@@ -270,13 +283,30 @@ window.onload = async function () {
updateFeatureMenuItemLabel("loopButton", window.i18nextInstance.t("pages.content.features.loopButton.label"));
break;
}
+ case "automaticTheaterModeChange": {
+ // Get the player element
+ const playerContainer = isWatchPage()
+ ? (document.querySelector("div#movie_player") as YouTubePlayerDiv | null)
+ : isShortsPage()
+ ? (document.querySelector("div#shorts-player") as YouTubePlayerDiv | null)
+ : null;
+ // If player element is not available, return
+ if (!playerContainer) return;
+ // Get the size button
+ const sizeButton = document.querySelector("button.ytp-size-button") as HTMLButtonElement | null;
+ // If the size button is not available return
+ if (!sizeButton) return;
+ sizeButton.click();
+
+ break;
+ }
default: {
return;
}
}
});
sendContentOnlyMessage("pageLoaded", undefined);
-};
+});
window.onbeforeunload = function () {
eventManager.removeAllEventListeners();
element.remove();
diff --git a/src/pages/inject/index.tsx b/src/pages/inject/index.tsx
index 4ca428f0..cefd3eb0 100644
--- a/src/pages/inject/index.tsx
+++ b/src/pages/inject/index.tsx
@@ -1,5 +1,5 @@
-import type { ContentSendOnlyMessageMappings, Messages, StorageChanges, configuration } from "@/src/@types";
import type { AvailableLocales } from "@/src/i18n";
+import type { ContentSendOnlyMessageMappings, Messages, StorageChanges, configuration } from "@/src/types";
import { getVideoHistory, setVideoHistory } from "@/src/features/videoHistory/utils";
import { parseReviver, parseStoredValue, sendExtensionMessage, sendExtensionOnlyMessage } from "@/src/utils/utilities";
@@ -158,6 +158,11 @@ const storageChangeHandler = async (changes: StorageChanges, areaName: string) =
const keyActions: {
[K in keyof configuration]?: () => void;
} = {
+ enable_automatic_theater_mode: () => {
+ sendExtensionOnlyMessage("automaticTheaterModeChange", {
+ automaticTheaterModeEnabled: castedChanges.enable_automatic_theater_mode.newValue
+ });
+ },
enable_forced_playback_speed: () => {
sendExtensionOnlyMessage("playerSpeedChange", {
enableForcedPlaybackSpeed: castedChanges.enable_forced_playback_speed.newValue,
diff --git a/src/@types/index.ts b/src/types/index.ts
similarity index 93%
rename from src/@types/index.ts
rename to src/types/index.ts
index bf41536c..15a9fd5e 100644
--- a/src/@types/index.ts
+++ b/src/types/index.ts
@@ -1,11 +1,9 @@
-import type { YouTubePlayer } from "node_modules/@types/youtube-player/dist/types";
+import type { YouTubePlayer } from "youtube-player/dist/types";
import z from "zod";
import type { AvailableLocales } from "../i18n";
import type { FeatureName } from "../utils/EventManager";
-
-/* eslint-disable no-mixed-spaces-and-tabs */
export type Writeable = { -readonly [P in keyof T]: T[P] };
export type DeepWriteable = { -readonly [P in keyof T]: DeepWriteable };
export const onScreenDisplayColor = ["red", "green", "blue", "yellow", "orange", "purple", "pink", "white"] as const;
@@ -38,8 +36,11 @@ export type ScreenshotType = (typeof screenshotType)[number];
export const screenshotFormat = ["png", "jpg", "webp"] as const;
export type ScreenshotFormat = (typeof screenshotFormat)[number];
+export const modifierKey = ["altKey", "ctrlKey", "shiftKey"] as const;
+export type ModifierKey = (typeof modifierKey)[number];
export type configuration = {
+ enable_automatic_theater_mode: boolean;
enable_automatically_set_quality: boolean;
enable_forced_playback_speed: boolean;
enable_hide_scrollbar: boolean;
@@ -49,6 +50,7 @@ export type configuration = {
enable_remember_last_volume: boolean;
enable_screenshot_button: boolean;
enable_scroll_wheel_volume_control: boolean;
+ enable_scroll_wheel_volume_control_modifier_key: boolean;
enable_video_history: boolean;
enable_volume_boost: boolean;
language: AvailableLocales;
@@ -66,10 +68,12 @@ export type configuration = {
};
screenshot_format: ScreenshotFormat;
screenshot_save_as: ScreenshotType;
+ scroll_wheel_volume_control_modifier_key: ModifierKey;
volume_adjustment_steps: number;
volume_boost_amount: number;
};
export type configurationKeys = keyof configuration;
+export type configurationId = configurationKeys;
export type VideoHistoryStatus = "watched" | "watching";
export type VideoHistoryEntry = {
id: string;
@@ -108,6 +112,7 @@ export type ContentSendOnlyMessageMappings = {
setRememberedVolume: SendDataMessage<"send_data", "content", "setRememberedVolume", { shortsPageVolume?: number; watchPageVolume?: number }>;
};
export type ExtensionSendOnlyMessageMappings = {
+ automaticTheaterModeChange: DataResponseMessage<"automaticTheaterModeChange", { automaticTheaterModeEnabled: boolean }>;
hideScrollBarChange: DataResponseMessage<"hideScrollBarChange", { hideScrollBarEnabled: boolean }>;
languageChange: DataResponseMessage<"languageChange", { language: AvailableLocales }>;
loopButtonChange: DataResponseMessage<"loopButtonChange", { loopButtonEnabled: boolean }>;
diff --git a/src/utils/EventManager.ts b/src/utils/EventManager.ts
index a24e26fb..79c5a147 100644
--- a/src/utils/EventManager.ts
+++ b/src/utils/EventManager.ts
@@ -1,4 +1,5 @@
export type FeatureName =
+ | "automaticTheaterMode"
| "featureMenu"
| "hideScrollBar"
| "loopButton"
@@ -26,7 +27,8 @@ export type EventManager = {
target: HTMLElementTagNameMap[keyof HTMLElementTagNameMap],
eventName: T,
callback: EventCallback,
- featureName: FeatureName
+ featureName: FeatureName,
+ options?: AddEventListenerOptions | boolean
) => void;
listeners: Map>;
@@ -45,7 +47,7 @@ export type EventManager = {
export const eventManager: EventManager = {
// Map of feature names to a map of targets to
// Adds an event listener for the given target, eventName, and featureName
- addEventListener: function (target, eventName, callback, featureName) {
+ addEventListener: function (target, eventName, callback, featureName, options) {
// Get the map of target listeners for the given featureName
const targetListeners = this.listeners.get(featureName) || new Map();
// Store the event listener info object in the map
@@ -53,7 +55,7 @@ export const eventManager: EventManager = {
// Store the map of target listeners for the given featureName
this.listeners.set(featureName, targetListeners);
// Add the event listener to the target
- target.addEventListener(eventName, callback);
+ target.addEventListener(eventName, callback, options);
},
// event listener info objects
diff --git a/src/utils/constants.ts b/src/utils/constants.ts
index 33751f6e..232defba 100644
--- a/src/utils/constants.ts
+++ b/src/utils/constants.ts
@@ -1,19 +1,20 @@
import z from "zod";
-import type { TypeToPartialZodSchema, configuration } from "../@types";
+import type { TypeToPartialZodSchema, configuration } from "../types";
+import { availableLocales } from "../i18n/index";
import {
+ modifierKey,
onScreenDisplayColor,
onScreenDisplayPosition,
onScreenDisplayType,
screenshotFormat,
screenshotType,
youtubePlayerQualityLevel
-} from "../@types";
-import { availableLocales } from "../i18n/index";
+} from "../types";
export const outputFolderName = "dist";
export const defaultConfiguration = {
- // Options
+ enable_automatic_theater_mode: false,
enable_automatically_set_quality: false,
enable_forced_playback_speed: false,
enable_hide_scrollbar: false,
@@ -22,12 +23,11 @@ export const defaultConfiguration = {
enable_remaining_time: false,
enable_remember_last_volume: false,
enable_screenshot_button: false,
- // General
enable_scroll_wheel_volume_control: false,
+ enable_scroll_wheel_volume_control_modifier_key: false,
enable_video_history: false,
enable_volume_boost: false,
language: "en-US",
- // Images
osd_display_color: "white",
osd_display_hide_time: 750,
osd_display_opacity: 75,
@@ -38,11 +38,13 @@ export const defaultConfiguration = {
player_speed: 1,
screenshot_format: "png",
screenshot_save_as: "file",
+ scroll_wheel_volume_control_modifier_key: "ctrlKey",
volume_adjustment_steps: 5,
volume_boost_amount: 1
} satisfies configuration;
export const configurationImportSchema: TypeToPartialZodSchema = z.object({
+ enable_automatic_theater_mode: z.boolean().optional(),
enable_automatically_set_quality: z.boolean().optional(),
enable_forced_playback_speed: z.boolean().optional(),
enable_hide_scrollbar: z.boolean().optional(),
@@ -52,6 +54,7 @@ export const configurationImportSchema: TypeToPartialZodSchema =
enable_remember_last_volume: z.boolean().optional(),
enable_screenshot_button: z.boolean().optional(),
enable_scroll_wheel_volume_control: z.boolean().optional(),
+ enable_scroll_wheel_volume_control_modifier_key: z.boolean().optional(),
enable_video_history: z.boolean().optional(),
enable_volume_boost: z.boolean().optional(),
language: z.enum(availableLocales).optional(),
@@ -66,6 +69,7 @@ export const configurationImportSchema: TypeToPartialZodSchema =
remembered_volume: z.number().optional(),
screenshot_format: z.enum(screenshotFormat).optional(),
screenshot_save_as: z.enum(screenshotType).optional(),
+ scroll_wheel_volume_control_modifier_key: z.enum(modifierKey).optional(),
volume_adjustment_steps: z.number().min(1).max(100).optional(),
volume_boost_amount: z.number().optional()
});
diff --git a/src/utils/utilities.ts b/src/utils/utilities.ts
index bda3985a..e537026d 100644
--- a/src/utils/utilities.ts
+++ b/src/utils/utilities.ts
@@ -1,3 +1,6 @@
+import { type ClassValue, clsx } from "clsx";
+import { twMerge } from "tailwind-merge";
+
import type {
ContentSendOnlyMessageMappings,
ExtensionSendOnlyMessageMappings,
@@ -6,13 +9,11 @@ import type {
Messages,
Selector,
SendDataMessage,
- YoutubePlayerQualityLabel,
+ YoutubePlayerQualityLevel,
configuration
-} from "@/src/@types";
-
-import { type ClassValue, clsx } from "clsx";
-import { twMerge } from "tailwind-merge";
+} from "../types";
+import { youtubePlayerQualityLevel } from "../types";
import { type FeatureName, eventManager } from "./EventManager";
export const isStrictEqual = (value1: unknown) => (value2: unknown) => value1 === value2;
@@ -34,25 +35,41 @@ export const round = (value: number, decimals = 0) => Number(`${Math.round(Numbe
export const toDivisible = (value: number, divider: number): number => Math.ceil(value / divider) * divider;
-export const chooseClosetQuality = (num: YoutubePlayerQualityLabel, arr: YoutubePlayerQualityLabel[]): YoutubePlayerQualityLabel => {
- const parsedNum = parseInt(num, 10);
- let [curr] = arr;
- let currDiff = Math.abs(parsedNum - parseInt(curr));
+export function chooseClosestQuality(
+ selectedQuality: YoutubePlayerQualityLevel,
+ availableQualities: YoutubePlayerQualityLevel[]
+): YoutubePlayerQualityLevel | null {
+ // If there are no available qualities, return null
+ if (availableQualities.length === 0) {
+ return null;
+ }
- for (let i = 1; i < arr.length; i++) {
- const label = arr.at(i);
- if (!label) continue;
- const parsedLabel = parseInt(label);
- const diff = Math.abs(parsedNum - parsedLabel);
+ // Find the index of the selected quality in the array
+ const selectedIndex = youtubePlayerQualityLevel.indexOf(selectedQuality);
- if (diff < currDiff) {
- curr = label;
- currDiff = diff;
- }
+ // If the selected quality is not in the array, return null
+ if (selectedIndex === -1) {
+ return null;
}
- return curr;
-};
+ // Find the available quality levels that are closest to the selected quality level
+ const closestQualities = availableQualities.reduce(
+ (acc, quality) => {
+ const qualityIndex = youtubePlayerQualityLevel.indexOf(quality);
+ if (qualityIndex !== -1) {
+ acc.push({ difference: Math.abs(selectedIndex - qualityIndex), quality });
+ }
+ return acc;
+ },
+ [] as { difference: number; quality: YoutubePlayerQualityLevel }[]
+ );
+
+ // Sort the closest qualities by difference in ascending order
+ closestQualities.sort((a, b) => a.difference - b.difference);
+
+ // Return the quality level with the minimum difference
+ return closestQualities[0].quality;
+}
const BrowserColors = {
BgBlack: "background-color: black; color: white;",
BgBlue: "background-color: blue; color: white;",
@@ -487,3 +504,15 @@ export function createTooltip({ element, featureName, id, text }: { element: HTM
export function cn(...inputs: ClassValue[]) {
return twMerge(clsx(inputs));
}
+// Utility function to create and style an element
+export function createStyledElement(
+ elementId: ID,
+ elementType: K,
+ styles: Partial
+): HTMLElementTagNameMap[K] {
+ const elementExists = document.getElementById(elementId) !== null;
+ const element = (elementExists ? document.getElementById(elementId) : document.createElement(elementType)) as HTMLElementTagNameMap[K];
+ if (!element.id) element.id = elementId;
+ Object.assign(element.style, styles);
+ return element;
+}
diff --git a/yarn.lock b/yarn.lock
index 34d73b23..400442d5 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -195,10 +195,10 @@
minimatch "^3.1.2"
strip-json-comments "^3.1.1"
-"@eslint/js@8.53.0":
- version "8.53.0"
- resolved "https://registry.npmjs.org/@eslint/js/-/js-8.53.0.tgz"
- integrity sha512-Kn7K8dx/5U6+cT1yEhpX1w4PCSg0M+XyRILPgvwcEBjerFWCwQj5sbr3/VmxqV0JGHCBCzyd6LxypEuehypY1w==
+"@eslint/js@8.54.0":
+ version "8.54.0"
+ resolved "https://registry.yarnpkg.com/@eslint/js/-/js-8.54.0.tgz#4fab9a2ff7860082c304f750e94acd644cf984cf"
+ integrity sha512-ut5V+D+fOoWPgGGNj83GGjnntO39xDy6DWxO0wb7Jp3DcMX0TfIqdzHF85VTQkerdyGmuuMD9AKAo5KiNlf/AQ==
"@formkit/auto-animate@^0.8.1":
version "0.8.1"
@@ -805,9 +805,9 @@
resolved "https://registry.npmjs.org/@swc/core-win32-x64-msvc/-/core-win32-x64-msvc-1.3.96.tgz"
integrity sha512-4VbSAniIu0ikLf5mBX81FsljnfqjoVGleEkCQv4+zRlyZtO3FHoDPkeLVoy6WRlj7tyrRcfUJ4mDdPkbfTO14g==
-"@swc/core@^1.3.95":
+"@swc/core@^1.3.96":
version "1.3.96"
- resolved "https://registry.npmjs.org/@swc/core/-/core-1.3.96.tgz"
+ resolved "https://registry.yarnpkg.com/@swc/core/-/core-1.3.96.tgz#f04d58b227ceed2fee6617ce2cdddf21d0803f96"
integrity sha512-zwE3TLgoZwJfQygdv2SdCK9mRLYluwDOM53I+dT6Z5ZvrgVENmY3txvWDvduzkV+/8IuvrRbVezMpxcojadRdQ==
dependencies:
"@swc/counter" "^0.1.1"
@@ -920,9 +920,9 @@
integrity sha512-dRLjCWHYg4oaA77cxO64oO+7JwCwnIzkZPdrrC71jQmQtlhM556pwKo5bUzqvZndkVbeFLIIi+9TC40JNF5hNQ==
"@types/node@*", "@types/node@^20.9.0":
- version "20.9.0"
- resolved "https://registry.npmjs.org/@types/node/-/node-20.9.0.tgz"
- integrity sha512-nekiGu2NDb1BcVofVcEKMIwzlx4NjHlcjhoxxKBNLtz15Y1z7MYf549DFvkHSId02Ax6kGwWntIBPC3l/JZcmw==
+ version "20.9.2"
+ resolved "https://registry.yarnpkg.com/@types/node/-/node-20.9.2.tgz#002815c8e87fe0c9369121c78b52e800fadc0ac6"
+ integrity sha512-WHZXKFCEyIUJzAwh3NyyTHYSR35SevJ6mZ1nWwJafKtiQbqRTIKSRcw3Ma3acqgsent3RRDqeVwpHntMk+9irg==
dependencies:
undici-types "~5.26.4"
@@ -1007,14 +1007,6 @@
"@typescript-eslint/visitor-keys" "6.11.0"
debug "^4.3.4"
-"@typescript-eslint/scope-manager@6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.10.0.tgz"
- integrity sha512-TN/plV7dzqqC2iPNf1KrxozDgZs53Gfgg5ZHyw8erd6jd5Ta/JIEcdCheXFt9b1NYb93a1wmIIVW/2gLkombDg==
- dependencies:
- "@typescript-eslint/types" "6.10.0"
- "@typescript-eslint/visitor-keys" "6.10.0"
-
"@typescript-eslint/scope-manager@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/scope-manager/-/scope-manager-6.11.0.tgz#621f603537c89f4d105733d949aa4d55eee5cea8"
@@ -1023,14 +1015,6 @@
"@typescript-eslint/types" "6.11.0"
"@typescript-eslint/visitor-keys" "6.11.0"
-"@typescript-eslint/type-utils@6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.10.0.tgz"
- integrity sha512-wYpPs3hgTFblMYwbYWPT3eZtaDOjbLyIYuqpwuLBBqhLiuvJ+9sEp2gNRJEtR5N/c9G1uTtQQL5AhV0fEPJYcg==
- dependencies:
- "@typescript-eslint/types" "6.11.0"
- "@typescript-eslint/visitor-keys" "6.11.0"
-
"@typescript-eslint/type-utils@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/type-utils/-/type-utils-6.11.0.tgz#d0b8b1ab6c26b974dbf91de1ebc5b11fea24e0d1"
@@ -1041,29 +1025,11 @@
debug "^4.3.4"
ts-api-utils "^1.0.1"
-"@typescript-eslint/types@6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.10.0.tgz"
- integrity sha512-36Fq1PWh9dusgo3vH7qmQAj5/AZqARky1Wi6WpINxB6SkQdY5vQoT2/7rW7uBIsPDcvvGCLi4r10p0OJ7ITAeg==
-
"@typescript-eslint/types@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/types/-/types-6.11.0.tgz#8ad3aa000cbf4bdc4dcceed96e9b577f15e0bf53"
integrity sha512-ZbEzuD4DwEJxwPqhv3QULlRj8KYTAnNsXxmfuUXFCxZmO6CF2gM/y+ugBSAQhrqaJL3M+oe4owdWunaHM6beqA==
-"@typescript-eslint/typescript-estree@6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.10.0.tgz"
- integrity sha512-ek0Eyuy6P15LJVeghbWhSrBCj/vJpPXXR+EpaRZqou7achUWL8IdYnMSC5WHAeTWswYQuP2hAZgij/bC9fanBg==
- dependencies:
- "@typescript-eslint/types" "6.10.0"
- "@typescript-eslint/visitor-keys" "6.10.0"
- debug "^4.3.4"
- globby "^11.1.0"
- is-glob "^4.0.3"
- semver "^7.5.4"
- ts-api-utils "^1.0.1"
-
"@typescript-eslint/typescript-estree@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/typescript-estree/-/typescript-estree-6.11.0.tgz#7b52c12a623bf7f8ec7f8a79901b9f98eb5c7990"
@@ -1077,10 +1043,10 @@
semver "^7.5.4"
ts-api-utils "^1.0.1"
-"@typescript-eslint/utils@6.10.0", "@typescript-eslint/utils@^6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.10.0.tgz"
- integrity sha512-v+pJ1/RcVyRc0o4wAGux9x42RHmAjIGzPRo538Z8M1tVx6HOnoQBCX/NoadHQlZeC+QO2yr4nNSFWOoraZCAyg==
+"@typescript-eslint/utils@6.11.0", "@typescript-eslint/utils@^6.10.0":
+ version "6.11.0"
+ resolved "https://registry.yarnpkg.com/@typescript-eslint/utils/-/utils-6.11.0.tgz#11374f59ef4cea50857b1303477c08aafa2ca604"
+ integrity sha512-p23ibf68fxoZy605dc0dQAEoUsoiNoP3MD9WQGiHLDuTSOuqoTsa4oAy+h3KDkTcxbbfOtUjb9h3Ta0gT4ug2g==
dependencies:
"@eslint-community/eslint-utils" "^4.4.0"
"@types/json-schema" "^7.0.12"
@@ -1090,14 +1056,6 @@
"@typescript-eslint/typescript-estree" "6.11.0"
semver "^7.5.4"
-"@typescript-eslint/visitor-keys@6.10.0":
- version "6.10.0"
- resolved "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.10.0.tgz"
- integrity sha512-xMGluxQIEtOM7bqFCo+rCMh5fqI+ZxV5RUUOa29iVPz1OgCZrtc7rFnz5cLUazlkPKYqX+75iuDq7m0HQ48nCg==
- dependencies:
- "@typescript-eslint/types" "6.10.0"
- eslint-visitor-keys "^3.4.1"
-
"@typescript-eslint/visitor-keys@6.11.0":
version "6.11.0"
resolved "https://registry.yarnpkg.com/@typescript-eslint/visitor-keys/-/visitor-keys-6.11.0.tgz#d991538788923f92ec40d44389e7075b359f3458"
@@ -1112,11 +1070,11 @@
integrity sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==
"@vitejs/plugin-react-swc@^3.4.1":
- version "3.4.1"
- resolved "https://registry.npmjs.org/@vitejs/plugin-react-swc/-/plugin-react-swc-3.4.1.tgz"
- integrity sha512-7YQOQcVV5x1luD8nkbCDdyYygFvn1hjqJk68UvNAzY2QG4o4N5EwAhLLFNOcd1HrdMwDl0VElP8VutoWf9IvJg==
+ version "3.5.0"
+ resolved "https://registry.yarnpkg.com/@vitejs/plugin-react-swc/-/plugin-react-swc-3.5.0.tgz#1fadff5148003e8091168c431e44c850f9a39e74"
+ integrity sha512-1PrOvAaDpqlCV+Up8RkAh9qaiUjoDUcjtttyhXDKw53XA6Ve16SOp6cCOpRs8Dj8DqUQs6eTW5YkLcLJjrXAig==
dependencies:
- "@swc/core" "^1.3.95"
+ "@swc/core" "^1.3.96"
JSONStream@^1.3.5:
version "1.3.5"
@@ -2345,9 +2303,9 @@ eslint-plugin-no-secrets@^0.8.9:
integrity sha512-CqaBxXrImABCtxMWspAnm8d5UKkpNylC7zqVveb+fJHEvsSiNGJlSWzdSIvBUnW1XhJXkzifNIZQC08rEII5Ng==
eslint-plugin-perfectionist@^2.3.0:
- version "2.3.0"
- resolved "https://registry.npmjs.org/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.3.0.tgz"
- integrity sha512-T/1HOysrsyExPr/N5apy3XFhejYqIturtejlSbTGy0WCw5dt72FDT92NOvRRKJvx8lftZDJ8AEIs5nHk9Pfa9Q==
+ version "2.4.0"
+ resolved "https://registry.yarnpkg.com/eslint-plugin-perfectionist/-/eslint-plugin-perfectionist-2.4.0.tgz#9a261c2b35c79fd581261888c64d711f96de7395"
+ integrity sha512-til+vyf56wAUgFv5guBA1Zo5lTw9xj2kCeK/g+9NBtsRy1rkGrlqnvxYNuFExcK3VsPhUUtx5UdScEDz9ahQ5Q==
dependencies:
"@typescript-eslint/utils" "^6.10.0"
minimatch "^9.0.3"
@@ -2415,14 +2373,14 @@ eslint-visitor-keys@^3.3.0, eslint-visitor-keys@^3.4.1, eslint-visitor-keys@^3.4
integrity sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==
eslint@^8.53.0:
- version "8.53.0"
- resolved "https://registry.npmjs.org/eslint/-/eslint-8.53.0.tgz"
- integrity sha512-N4VuiPjXDUa4xVeV/GC/RV3hQW9Nw+Y463lkWaKKXKYMvmRiRDAtfpuPFLN+E1/6ZhyR8J2ig+eVREnYgUsiag==
+ version "8.54.0"
+ resolved "https://registry.yarnpkg.com/eslint/-/eslint-8.54.0.tgz#588e0dd4388af91a2e8fa37ea64924074c783537"
+ integrity sha512-NY0DfAkM8BIZDVl6PgSa1ttZbx3xHgJzSNJKYcQglem6CppHyMhRIQkBVSSMaSRnLhig3jsDbEzOjwCVt4AmmA==
dependencies:
"@eslint-community/eslint-utils" "^4.2.0"
"@eslint-community/regexpp" "^4.6.1"
"@eslint/eslintrc" "^2.1.3"
- "@eslint/js" "8.53.0"
+ "@eslint/js" "8.54.0"
"@humanwhocodes/config-array" "^0.11.13"
"@humanwhocodes/module-importer" "^1.0.1"
"@nodelib/fs.walk" "^1.2.8"
@@ -3116,11 +3074,12 @@ import-fresh@^3.2.1:
parent-module "^1.0.0"
resolve-from "^4.0.0"
-import-from-esm@^1.0.3:
- version "1.2.1"
- resolved "https://registry.npmjs.org/import-from-esm/-/import-from-esm-1.2.1.tgz"
- integrity sha512-Nly5Ab75rWZmOwtMa0B0NQNnHGcHOQ2zkU/bVENwK2lbPq+kamPDqNKNJ0hF7w7lR/ETD5nGgJq0XbofsZpYCA==
+import-from-esm@^1.0.3, import-from-esm@^1.3.1:
+ version "1.3.3"
+ resolved "https://registry.yarnpkg.com/import-from-esm/-/import-from-esm-1.3.3.tgz#eea1c4ad86a54bf425b3b71fca56d50215ccc6b7"
+ integrity sha512-U3Qt/CyfFpTUv6LOP2jRTLYjphH6zg3okMfHbyqRa/W2w6hr8OsJWVggNlR4jxuojQy81TgTJTxgSkyoteRGMQ==
dependencies:
+ debug "^4.3.4"
import-meta-resolve "^4.0.0"
import-meta-resolve@^4.0.0:
@@ -5151,9 +5110,9 @@ scheduler@^0.23.0:
loose-envify "^1.1.0"
semantic-release@^22.0.7:
- version "22.0.7"
- resolved "https://registry.npmjs.org/semantic-release/-/semantic-release-22.0.7.tgz"
- integrity sha512-Stx23Hjn7iU8GOAlhG3pHlR7AoNEahj9q7lKBP0rdK2BasGtJ4AWYh3zm1u3SCMuFiA8y4CE/Gu4RGKau1WiaQ==
+ version "22.0.8"
+ resolved "https://registry.yarnpkg.com/semantic-release/-/semantic-release-22.0.8.tgz#13470a2af04e42fd767da278bcdddb0e91c8fb3f"
+ integrity sha512-55rb31jygqIYsGU/rY+gXXm2fnxBIWo9azOjxbqKsPnq7p70zwZ5v+xnD7TxJC+zvS3sy1eHLGXYWCaX3WI76A==
dependencies:
"@semantic-release/commit-analyzer" "^11.0.0"
"@semantic-release/error" "^4.0.0"
@@ -5171,6 +5130,7 @@ semantic-release@^22.0.7:
git-log-parser "^1.2.0"
hook-std "^3.0.0"
hosted-git-info "^7.0.0"
+ import-from-esm "^1.3.1"
lodash-es "^4.17.21"
marked "^9.0.0"
marked-terminal "^6.0.0"