From a9ec5ea44e4deaedf1eb41e656562acc62bba168 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:03:08 -0500 Subject: [PATCH 01/13] fix typo of extra double quote --- menu/index.html | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/menu/index.html b/menu/index.html index d4fab8b8b..70f4ad5f1 100644 --- a/menu/index.html +++ b/menu/index.html @@ -1,5 +1,5 @@ -ImprovedTube +ImprovedTube From f1a4ddae5063196a03a0dd9137e098b4d94c4805 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:06:31 -0500 Subject: [PATCH 02/13] Create .gitignore file --- .gitignore | 2 ++ 1 file changed, 2 insertions(+) create mode 100644 .gitignore diff --git a/.gitignore b/.gitignore new file mode 100644 index 000000000..3ec544c7a --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +node_modules/ +.env \ No newline at end of file From ff67169eddddd986acb9aa876421984fb2a66074 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:07:12 -0500 Subject: [PATCH 03/13] Install dotenv and adjust version of eslint --- package-lock.json | 289 +++++++++++++++++++++++++++++++++------------- package.json | 3 +- 2 files changed, 208 insertions(+), 84 deletions(-) diff --git a/package-lock.json b/package-lock.json index b183c3f50..3a154fe7f 100644 --- a/package-lock.json +++ b/package-lock.json @@ -16,7 +16,8 @@ "devDependencies": { "@eslint/eslintrc": "latest", "@eslint/js": "latest", - "eslint": "^9.6.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.1", "globals": "^15.8.0", "jest": "^29.7.0", "jslint": "^0.12.1" @@ -646,20 +647,6 @@ "node": "^18.18.0 || ^20.9.0 || >=21.1.0" } }, - "node_modules/@eslint/config-array": { - "version": "0.17.0", - "resolved": "https://registry.npmjs.org/@eslint/config-array/-/config-array-0.17.0.tgz", - "integrity": "sha512-A68TBu6/1mHHuc5YJL0U0VVeGNiklLAL6rRmhTCP2B5XjWLMnrX+HkO+IAXyHvks5cyyY1jjK5ITPQ1HGS2EVA==", - "license": "Apache-2.0", - "dependencies": { - "@eslint/object-schema": "^2.1.4", - "debug": "^4.3.1", - "minimatch": "^3.1.2" - }, - "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" - } - }, "node_modules/@eslint/eslintrc": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-3.1.0.tgz", @@ -717,6 +704,7 @@ "version": "9.6.0", "resolved": "https://registry.npmjs.org/@eslint/js/-/js-9.6.0.tgz", "integrity": "sha512-D9B0/3vNg44ZeWbYMpBoXqNP4j6eQD5vNwIlGAuFRRzK/WtT/jvDQW3Bi9kkf3PMDMlM7Yi+73VLUsn5bJcl8A==", + "dev": true, "license": "MIT", "engines": { "node": "^18.18.0 || ^20.9.0 || >=21.1.0" @@ -752,13 +740,18 @@ "url": "https://github.com/sponsors/sindresorhus" } }, - "node_modules/@eslint/object-schema": { - "version": "2.1.4", - "resolved": "https://registry.npmjs.org/@eslint/object-schema/-/object-schema-2.1.4.tgz", - "integrity": "sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==", - "license": "Apache-2.0", + "node_modules/@humanwhocodes/config-array": { + "version": "0.13.0", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.13.0.tgz", + "integrity": "sha512-DZLEEqFWQFiyK6h5YIeynKx7JlvCYWL0cImfSRXZ9l4Sg2efkFGTuFf6vzXjK1cq6IYkU+Eg/JizXw+TD2vRNw==", + "deprecated": "Use @eslint/config-array instead", + "dependencies": { + "@humanwhocodes/object-schema": "^2.0.3", + "debug": "^4.3.1", + "minimatch": "^3.0.5" + }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": ">=10.10.0" } }, "node_modules/@humanwhocodes/module-importer": { @@ -774,18 +767,11 @@ "url": "https://github.com/sponsors/nzakas" } }, - "node_modules/@humanwhocodes/retry": { - "version": "0.3.0", - "resolved": "https://registry.npmjs.org/@humanwhocodes/retry/-/retry-0.3.0.tgz", - "integrity": "sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==", - "license": "Apache-2.0", - "engines": { - "node": ">=18.18" - }, - "funding": { - "type": "github", - "url": "https://github.com/sponsors/nzakas" - } + "node_modules/@humanwhocodes/object-schema": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-2.0.3.tgz", + "integrity": "sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==", + "deprecated": "Use @eslint/object-schema instead" }, "node_modules/@istanbuljs/load-nyc-config": { "version": "1.1.0", @@ -1307,6 +1293,11 @@ "integrity": "sha512-I4q9QU9MQv4oEOz4tAHJtNz1cwuLxn2F3xcc2iV5WdqLPpUnj30aUuxt1mAxYTG+oe8CZMV/+6rU4S4gRDzqtQ==", "dev": true }, + "node_modules/@ungap/structured-clone": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@ungap/structured-clone/-/structured-clone-1.2.0.tgz", + "integrity": "sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==" + }, "node_modules/abbrev": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz", @@ -1856,6 +1847,29 @@ "node": "^14.15.0 || ^16.10.0 || >=18.0.0" } }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dotenv": { + "version": "16.4.5", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.4.5.tgz", + "integrity": "sha512-ZmdL2rui+eB2YwhsWzjInR8LldtZHGDoQ1ugH85ppHKwpUHL7j7rN0Ti9NCnGiQbhaZ11FpR+7ao1dNsmduNUg==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, "node_modules/electron-to-chromium": { "version": "1.4.794", "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.794.tgz", @@ -1906,37 +1920,41 @@ } }, "node_modules/eslint": { - "version": "9.6.0", - "resolved": "https://registry.npmjs.org/eslint/-/eslint-9.6.0.tgz", - "integrity": "sha512-ElQkdLMEEqQNM9Njff+2Y4q2afHk7JpkPvrd7Xh7xefwgQynqPxwf55J7di9+MEibWUGdNjFF9ITG9Pck5M84w==", - "license": "MIT", + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.57.1.tgz", + "integrity": "sha512-ypowyDxpVSYpkXr9WPv2PAZCtNip1Mv5KTW0SCurXv/9iOpcrH9PaqUElksqEB6pChqHGDRCFTyrZlGhnLNGiA==", + "deprecated": "This version is no longer supported. Please see https://eslint.org/version-support for other options.", "dependencies": { "@eslint-community/eslint-utils": "^4.2.0", "@eslint-community/regexpp": "^4.6.1", - "@eslint/config-array": "^0.17.0", - "@eslint/eslintrc": "^3.1.0", - "@eslint/js": "9.6.0", + "@eslint/eslintrc": "^2.1.4", + "@eslint/js": "8.57.1", + "@humanwhocodes/config-array": "^0.13.0", "@humanwhocodes/module-importer": "^1.0.1", - "@humanwhocodes/retry": "^0.3.0", "@nodelib/fs.walk": "^1.2.8", + "@ungap/structured-clone": "^1.2.0", "ajv": "^6.12.4", "chalk": "^4.0.0", "cross-spawn": "^7.0.2", "debug": "^4.3.2", + "doctrine": "^3.0.0", "escape-string-regexp": "^4.0.0", - "eslint-scope": "^8.0.1", - "eslint-visitor-keys": "^4.0.0", - "espree": "^10.1.0", - "esquery": "^1.5.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", "esutils": "^2.0.2", "fast-deep-equal": "^3.1.3", - "file-entry-cache": "^8.0.0", + "file-entry-cache": "^6.0.1", "find-up": "^5.0.0", "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", "ignore": "^5.2.0", "imurmurhash": "^0.1.4", "is-glob": "^4.0.0", "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", "json-stable-stringify-without-jsonify": "^1.0.1", "levn": "^0.4.1", "lodash.merge": "^4.6.2", @@ -1950,10 +1968,10 @@ "eslint": "bin/eslint.js" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { - "url": "https://eslint.org/donate" + "url": "https://opencollective.com/eslint" } }, "node_modules/eslint-plugin-compat": { @@ -2064,16 +2082,15 @@ } }, "node_modules/eslint-scope": { - "version": "8.0.1", - "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-8.0.1.tgz", - "integrity": "sha512-pL8XjgP4ZOmmwfFE8mEhSxA7ZY4C+LWyqjQ3o4yWkkmD0qcMT9kkW3zWHOczhWcjTSgqycYAgwSlXvZltv65og==", - "license": "BSD-2-Clause", + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", "dependencies": { "esrecurse": "^4.3.0", "estraverse": "^5.2.0" }, "engines": { - "node": "^18.18.0 || ^20.9.0 || >=21.1.0" + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" }, "funding": { "url": "https://opencollective.com/eslint" @@ -2091,6 +2108,41 @@ "url": "https://opencollective.com/eslint" } }, + "node_modules/eslint/node_modules/@eslint/eslintrc": { + "version": "2.1.4", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.4.tgz", + "integrity": "sha512-269Z39MS6wVJtsoUl10L60WdkhJVdPG24Q4eZTH3nnF6lpvSShEK3wQjDX9JRWAUPvPh7COouPpU9IrqaZFvtQ==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/@eslint/js": { + "version": "8.57.1", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.57.1.tgz", + "integrity": "sha512-d9zaMRSTIKDLhctzH12MtXvJKSSUhaHcjV+2Z+GK+EEY7XKpP5yR4x+N3TAcHTcu963nIr+TMcCb4DBCYX1z6Q==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint/node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, "node_modules/eslint/node_modules/escape-string-regexp": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", @@ -2103,6 +2155,33 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint/node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, "node_modules/eslint/node_modules/find-up": { "version": "5.0.0", "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", @@ -2119,6 +2198,31 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/globals": { + "version": "13.24.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.24.0.tgz", + "integrity": "sha512-AhO5QUcj8llrbG09iWhPU2B204J1xnPeL8kQmVorSsy+Sjj1sk8gIyh6cUocGmH4L0UuhAJy+hJMRA4mgA4mFQ==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint/node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, "node_modules/eslint/node_modules/locate-path": { "version": "6.0.0", "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", @@ -2149,6 +2253,17 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/eslint/node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/espree": { "version": "10.1.0", "resolved": "https://registry.npmjs.org/espree/-/espree-10.1.0.tgz", @@ -2194,7 +2309,6 @@ "version": "4.3.0", "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", - "license": "BSD-2-Clause", "dependencies": { "estraverse": "^5.2.0" }, @@ -2304,15 +2418,14 @@ } }, "node_modules/file-entry-cache": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-8.0.0.tgz", - "integrity": "sha512-XXTUwCvisa5oacNGRP9SfNtYBNAMi+RPwBFmblZEF7N7swHYQS6/Zfk7SRwx4D5j3CH211YNRco1DEMNVfZCnQ==", - "license": "MIT", + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", "dependencies": { - "flat-cache": "^4.0.0" + "flat-cache": "^3.0.4" }, "engines": { - "node": ">=16.0.0" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/fill-range": { @@ -2341,29 +2454,27 @@ } }, "node_modules/flat-cache": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-4.0.1.tgz", - "integrity": "sha512-f7ccFPK3SXFHpx15UIGyRJ/FJQctuKZ0zVuN3frBo4HnK3cay9VEW0R6yPYFHC0AgqhukPzKjq22t5DmAyqGyw==", - "license": "MIT", + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.2.0.tgz", + "integrity": "sha512-CYcENa+FtcUKLmhhqyctpclsq7QF38pKjZHsGNiSQF5r4FtoKDWabFDl3hzaEQMvT1LHEysw5twgLvpYYb4vbw==", "dependencies": { "flatted": "^3.2.9", - "keyv": "^4.5.4" + "keyv": "^4.5.3", + "rimraf": "^3.0.2" }, "engines": { - "node": ">=16" + "node": "^10.12.0 || >=12.0.0" } }, "node_modules/flatted": { - "version": "3.3.1", - "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.1.tgz", - "integrity": "sha512-X8cqMLLie7KsNUDSdzeN8FYK9rEt4Dt67OsG/DNGnYTSDBG4uFAJFBnUeiV+zCVAvwFy56IjM9sH51jVaEhNxw==", - "license": "ISC" + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.3.2.tgz", + "integrity": "sha512-AiwGJM8YcNOaobumgtng+6NHuOqC3A7MixFeDafM3X9cIUM+xUXoS5Vfgf+OihAYe20fxqNM9yPBXJzRtZ/4eA==" }, "node_modules/fs.realpath": { "version": "1.0.0", "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", - "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", - "dev": true + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" }, "node_modules/fsevents": { "version": "2.3.3", @@ -2432,7 +2543,6 @@ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", "deprecated": "Glob versions prior to v9 are no longer supported", - "dev": true, "dependencies": { "fs.realpath": "^1.0.0", "inflight": "^1.0.4", @@ -2479,6 +2589,11 @@ "integrity": "sha512-RbJ5/jmFcNNCcDV5o9eTnBLJ/HszWV0P73bc+Ff4nS/rJj+YaS6IGyiOL0VoBYX+l1Wrl3k63h/KrH+nhJ0XvQ==", "dev": true }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==" + }, "node_modules/has-flag": { "version": "4.0.0", "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", @@ -2580,7 +2695,6 @@ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", "deprecated": "This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful.", - "dev": true, "dependencies": { "once": "^1.3.0", "wrappy": "1" @@ -2589,8 +2703,7 @@ "node_modules/inherits": { "version": "2.0.4", "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", - "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", - "dev": true + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" }, "node_modules/is-arrayish": { "version": "0.2.1", @@ -3408,8 +3521,7 @@ "node_modules/json-buffer": { "version": "3.0.1", "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", - "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", - "license": "MIT" + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==" }, "node_modules/json-parse-even-better-errors": { "version": "2.3.1", @@ -3445,7 +3557,6 @@ "version": "4.5.4", "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.4.tgz", "integrity": "sha512-oxVHkHR/EJf2CNXnWxRLW6mg7JyCCUcG0DtEGmL2ctUo1PNTin1PUil+r/+4r5MpVgC/fn1kjsx7mjSujKqIpw==", - "license": "MIT", "dependencies": { "json-buffer": "3.0.1" } @@ -3654,7 +3765,6 @@ "version": "1.4.0", "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", - "dev": true, "dependencies": { "wrappy": "1" } @@ -3812,7 +3922,6 @@ "version": "1.0.1", "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", - "dev": true, "engines": { "node": ">=0.10.0" } @@ -4071,6 +4180,21 @@ "node": ">=0.10.0" } }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "deprecated": "Rimraf versions prior to v4 are no longer supported", + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, "node_modules/run-parallel": { "version": "1.2.0", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", @@ -4483,8 +4607,7 @@ "node_modules/wrappy": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", - "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", - "dev": true + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" }, "node_modules/write-file-atomic": { "version": "4.0.2", diff --git a/package.json b/package.json index 202a25153..b4667666c 100644 --- a/package.json +++ b/package.json @@ -13,7 +13,8 @@ "devDependencies": { "@eslint/eslintrc": "latest", "@eslint/js": "latest", - "eslint": "^9.6.0", + "dotenv": "^16.4.5", + "eslint": "^8.57.1", "globals": "^15.8.0", "jest": "^29.7.0", "jslint": "^0.12.1" From 859309d3dd4a020477fcf28a9261b89afdb0d6ad Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:24:37 -0500 Subject: [PATCH 04/13] add content.js file that act as web-browser site --- js&css/extension/content.js | 151 ++++++++++++++++++++++++++++++++++++ 1 file changed, 151 insertions(+) create mode 100644 js&css/extension/content.js diff --git a/js&css/extension/content.js b/js&css/extension/content.js new file mode 100644 index 000000000..9a6fef847 --- /dev/null +++ b/js&css/extension/content.js @@ -0,0 +1,151 @@ +// Function to get the video ID from the URL +function getVideoIdFromUrl() { + const url = window.location.href; + const urlObj = new URL(url); + return urlObj.searchParams.get("v"); +} + +// Send the video ID to the background script +function sendVideoIdToBackground() { + const videoId = getVideoIdFromUrl(); + if (videoId) { + console.log('Sending video ID to background:', videoId); + chrome.runtime.sendMessage({ action: 'store-video-id', videoId: videoId }, function(response) { + console.log('Response from background:', response); + }); + } +} + +function createChannelInfo(channelName, uploadTime, videoCount, customUrl) { + console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log + const container = document.createElement('div'); + container.className = 'channel-info'; + + // Add a line break after the upload time + const lineBreak = document.createElement('br'); + + if (uploadTime) { + const uploadTimeElement = document.createElement('span'); + uploadTimeElement.className = 'upload-time'; + uploadTimeElement.textContent = `${uploadTime}`; + container.appendChild(uploadTimeElement); + container.appendChild(lineBreak); + } + + const allVideosLink = document.createElement('a'); + allVideosLink.className = 'all-videos-link'; + allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; + allVideosLink.textContent = 'All videos'; + container.appendChild(allVideosLink); + + if (videoCount) { + const videoCountElement = document.createElement('span'); + videoCountElement.className = 'video-count'; + videoCountElement.textContent = ` (${videoCount} videos)`; + container.appendChild(videoCountElement); + container.appendChild(lineBreak); + } + + const viewDataLink = document.createElement('a'); + viewDataLink.className = 'view-data-link'; + viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; + viewDataLink.textContent = 'View video data'; + container.appendChild(viewDataLink); + + // Add custom CSS styles + const style = document.createElement('style'); + style.textContent = ` + .channel-info { + background-color: #f9f9f9; + border: 1px solid #ddd; + padding: 10px; + margin-top: 10px; + margin-left: 10px; + margin-right: 10px; + border-radius: 5px; + } + .channel-info .channel-name { + font-weight: bold; + display: block; + margin-bottom: 5px; + } + .channel-info .upload-time { + color: #555; + display: block; + margin-bottom: 5px; + } + .channel-info .all-videos-link, + .channel-info .view-data-link { + color: #1a73e8; + text-decoration: none; + margin-right: 10px; + } + .channel-info .all-videos-link:hover, + .channel-info .view-data-link:hover { + text-decoration: underline; + } + .channel-info .video-count { + color: #333; + display: block; + margin-top: 5px; + } + `; + document.head.appendChild(style); + + return container; +} + +document.addEventListener('DOMContentLoaded', function() { + sendVideoIdToBackground() + let previousVideoId = getVideoIdFromUrl(); + + // Listen for changes in the video URL and send the updated video ID + const observer = new MutationObserver(() => { + const currentVideoId = getVideoIdFromUrl(); + if (currentVideoId !== previousVideoId) { + previousVideoId = currentVideoId; + sendVideoIdToBackground(); + + // Check if the switch is on and fetch new-data + chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + } + }) + observer.observe(document.body, { childList: true, subtree: true }); + + // Listen for visibility changes to handle page navigation + document.addEventListener('visibilitychange', function() { + if (document.visibilityState === 'visible') { + // Check if the switch is on and re-fetch data if the user returns to the video page + chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + } + }); + + chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { + console.log('Received message from background:', message); // Debugging log + if (message.action === 'append-channel-info') { + console.log('Switch on Details mode'); + const {channelName, uploadTime, videoCount, customUrl} = message; + const channelInfo = createChannelInfo(channelName, uploadTime, videoCount, customUrl); + const targetElement = document.querySelector('ytd-video-owner-renderer.style-scope.ytd-watch-metadata'); + if (targetElement) { + // Removing existing channel info if present + const existingChannelInfo = targetElement.querySelector('.channel-info'); + if(existingChannelInfo) { + existingChannelInfo.remove(); + } + targetElement.appendChild(channelInfo); + } else { + console.error('Target element not found'); + } + } else if (message.action === 'remove-channel-info') { + console.log('Switch off Details mode'); + const targetElement = document.querySelector('.channel-info'); + console.log(targetElement) + if(targetElement) { + targetElement.remove(); + } else { + console.error('Channel info element not found'); + } + } + }); +}) \ No newline at end of file From a366023505f262a8dd7e6951872e9b1fb53545b2 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:25:05 -0500 Subject: [PATCH 05/13] add content.js as content_scripts --- manifest.json | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/manifest.json b/manifest.json index 7080c728c..29a024d49 100644 --- a/manifest.json +++ b/manifest.json @@ -52,7 +52,8 @@ "js&css/extension/www.youtube.com/general/general.js", "js&css/extension/www.youtube.com/appearance/sidebar/sidebar.js", "js&css/extension/www.youtube.com/appearance/comments/comments.js", - "js&css/extension/init.js" + "js&css/extension/init.js", + "js&css/extension/content.js" ], "matches": [ "https://www.youtube.com/*" From 0e4f0bdddc05b72ed76c542858392d6716b16429 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:26:15 -0500 Subject: [PATCH 06/13] create a detail switch to turn on/off details for video --- menu/skeleton-parts/channel.js | 71 +++++++++++++++++++++++++++++++++- 1 file changed, 70 insertions(+), 1 deletion(-) diff --git a/menu/skeleton-parts/channel.js b/menu/skeleton-parts/channel.js index 766b30d18..17379fdb3 100644 --- a/menu/skeleton-parts/channel.js +++ b/menu/skeleton-parts/channel.js @@ -53,6 +53,39 @@ extension.skeleton.main.layers.section.channel = { channel_compact_theme: { component: 'switch', text: 'compactTheme' + }, + channel_details_button: { + component: 'switch', + text: 'Details', + value: false, // Default state is off + on: { + change: async function (event) { + const apiKey = YOUTUBE_API_KEY; + const switchElement = event.target.closest('.satus-switch'); + const isChecked = switchElement && switchElement.dataset.value === 'true'; + console.log(isChecked) + try { + const videoId = await getCurrentVideoId(); + const videoInfo = await getVideoInfo(apiKey, videoId); + + const channelId = videoInfo.snippet.channelId + const channelInfo = await getChannelInfo(apiKey, channelId); + + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + console.log("Sending message to content.js"); + chrome.tabs.sendMessage(tabs[0].id, { + action: isChecked ? 'append-channel-info' : 'remove-channel-info', + channelName: channelInfo.channelName, + uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), + videoCount: channelInfo.videoCount, + customUrl: channelInfo.customUrl + }) + }); + } catch (error){ + console.error(error); + } + } + } } } }, @@ -93,4 +126,40 @@ extension.skeleton.main.layers.section.channel = { component: 'span', text: 'channel' } -}; \ No newline at end of file +}; + +async function getCurrentVideoId() { + return new Promise((resolve, reject) => { + chrome.storage.local.get('videoId', (result) => { + console.log('Retrieved video ID from storage:', result.videoId); // Debugging log + if (result.videoId) { + resolve(result.videoId); + } else { + reject('Video ID not found'); + } + }); + }); +} + +async function getVideoInfo(apiKey, videoId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); + const data = await response.json(); + const video = data?.items[0]; + console.log(video) + + return video; +} + +async function getChannelInfo(apiKey, channelId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/channels?id=${channelId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); + const data = await response.json(); + const channel = data.items[0]; + console.log(channel); + const customUrl = channel.snippet.customUrl; + const channelName = channel.snippet.title; + // const uploadTime = new Date(channel.snippet.publishedAt).toLocaleString(); + const videoCount = channel.statistics.videoCount; + + return {channelName, videoCount, customUrl}; +} + From a51a4d4dfa1b14e521566aad79f752725f4b3760 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Fri, 29 Nov 2024 00:27:09 -0500 Subject: [PATCH 07/13] handle the store-video-id and fetch-new-data from background site --- background.js | 60 +++++++++++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 58 insertions(+), 2 deletions(-) diff --git a/background.js b/background.js index 93473d422..b652de1a5 100644 --- a/background.js +++ b/background.js @@ -245,6 +245,7 @@ chrome.windows.onFocusChanged.addListener(function (wId) { chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { //console.log(message); //console.log(sender); + console.log('Message received in background:', message); switch (message.action || message.name || message) { case 'play': @@ -319,8 +320,63 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { } catch (error) { console.error(error); } } else { console.error('Permission is not granted.'); } }) - break - } + break; + + case 'store-video-id': + console.log('Storing video ID:', message.videoId); // Debugging log + if (message.videoId) { + chrome.storage.local.set({ videoId: message.videoId }, function() { + console.log('Video ID stored:', message.videoId); // Debugging log + sendResponse({ status: 'success' }); + }); + } else { + sendResponse({ status: 'error', message: 'No video ID provided' }); + } + return true; // Indicates that sendResponse will be called asynchronously + + case 'fetch-new-data': + chrome.storage.local.get('videoId', async function(result) { + const videoId = result.videoId; + const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key + try { + const videoInfo = await getVideoInfo(apiKey, videoId); + const channelId = videoInfo.snippet.channelId; + const channelInfo = await getChannelInfo(apiKey, channelId); + + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + console.log("Sending message to content.js"); + chrome.tabs.sendMessage(tabs[0].id, { + action: 'append-channel-info', + channelName: channelInfo.channelName, + uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), + videoCount: channelInfo.videoCount, + customUrl: channelInfo.customUrl + }); + }); + } catch (error) { + console.error(error); + } + }); + return true; + + } }); +async function getVideoInfo(apiKey, videoId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/videos?part=snippet&id=${videoId}&key=${apiKey}`); + const data = await response.json(); + return data.items[0]; +} + +async function getChannelInfo(apiKey, channelId) { + const response = await fetch(`https://www.googleapis.com/youtube/v3/channels?part=snippet,statistics&id=${channelId}&key=${apiKey}`); + const data = await response.json(); + const channel = data.items[0]; + + const channelName = channel.snippet.title; + const uploadTime = new Date(channel.snippet.publishedAt).toLocaleString(); + const videoCount = channel.statistics.videoCount; + const customUrl = channel.snippet.customUrl; + + return { channelName, uploadTime, videoCount, customUrl };} /*-----# UNINSTALL URL-----------------------------------*/ chrome.runtime.setUninstallURL('https://improvedtube.com/uninstalled'); From ce7d3985f164ee22e1c25d5c997a29c99fead874 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 16:53:43 -0500 Subject: [PATCH 08/13] Store the switch's state to the local storage. Comment all debug logging --- menu/skeleton-parts/channel.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/menu/skeleton-parts/channel.js b/menu/skeleton-parts/channel.js index 17379fdb3..683296d0b 100644 --- a/menu/skeleton-parts/channel.js +++ b/menu/skeleton-parts/channel.js @@ -63,7 +63,10 @@ extension.skeleton.main.layers.section.channel = { const apiKey = YOUTUBE_API_KEY; const switchElement = event.target.closest('.satus-switch'); const isChecked = switchElement && switchElement.dataset.value === 'true'; - console.log(isChecked) + + // Store the switch state in chrome.storage.local + chrome.storage.local.set( { switchState: isChecked } ); + try { const videoId = await getCurrentVideoId(); const videoInfo = await getVideoInfo(apiKey, videoId); @@ -72,7 +75,7 @@ extension.skeleton.main.layers.section.channel = { const channelInfo = await getChannelInfo(apiKey, channelId); chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - console.log("Sending message to content.js"); + //console.log("Sending message to content.js"); chrome.tabs.sendMessage(tabs[0].id, { action: isChecked ? 'append-channel-info' : 'remove-channel-info', channelName: channelInfo.channelName, @@ -131,7 +134,7 @@ extension.skeleton.main.layers.section.channel = { async function getCurrentVideoId() { return new Promise((resolve, reject) => { chrome.storage.local.get('videoId', (result) => { - console.log('Retrieved video ID from storage:', result.videoId); // Debugging log + //console.log('Retrieved video ID from storage:', result.videoId); // Debugging log if (result.videoId) { resolve(result.videoId); } else { @@ -145,7 +148,6 @@ async function getVideoInfo(apiKey, videoId) { const response = await fetch(`https://www.googleapis.com/youtube/v3/videos?id=${videoId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); const data = await response.json(); const video = data?.items[0]; - console.log(video) return video; } @@ -154,10 +156,8 @@ async function getChannelInfo(apiKey, channelId) { const response = await fetch(`https://www.googleapis.com/youtube/v3/channels?id=${channelId}&key=${apiKey}&part=snippet,contentDetails,statistics,status`); const data = await response.json(); const channel = data.items[0]; - console.log(channel); const customUrl = channel.snippet.customUrl; const channelName = channel.snippet.title; - // const uploadTime = new Date(channel.snippet.publishedAt).toLocaleString(); const videoCount = channel.statistics.videoCount; return {channelName, videoCount, customUrl}; From a6367d9c8dbb9c1268f0e668ff223ebec687663a Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 16:54:28 -0500 Subject: [PATCH 09/13] Add new action 'check-switch-state' and comment debug logging --- background.js | 62 +++++++++++++++++++++++++++++---------------------- 1 file changed, 35 insertions(+), 27 deletions(-) diff --git a/background.js b/background.js index b652de1a5..f082122f5 100644 --- a/background.js +++ b/background.js @@ -323,41 +323,48 @@ chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { break; case 'store-video-id': - console.log('Storing video ID:', message.videoId); // Debugging log + //console.log('Storing video ID:', message.videoId); // Debugging log if (message.videoId) { chrome.storage.local.set({ videoId: message.videoId }, function() { - console.log('Video ID stored:', message.videoId); // Debugging log + //console.log('Video ID stored:', message.videoId); // Debugging log sendResponse({ status: 'success' }); }); } else { sendResponse({ status: 'error', message: 'No video ID provided' }); } return true; // Indicates that sendResponse will be called asynchronously - - case 'fetch-new-data': - chrome.storage.local.get('videoId', async function(result) { - const videoId = result.videoId; - const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key - try { - const videoInfo = await getVideoInfo(apiKey, videoId); - const channelId = videoInfo.snippet.channelId; - const channelInfo = await getChannelInfo(apiKey, channelId); - - chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { - console.log("Sending message to content.js"); - chrome.tabs.sendMessage(tabs[0].id, { - action: 'append-channel-info', - channelName: channelInfo.channelName, - uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), - videoCount: channelInfo.videoCount, - customUrl: channelInfo.customUrl - }); + + case 'check-switch-state': + chrome.storage.local.get('switchState', function(result) { + //console.log('Switch state:', result.switchState); + sendResponse( {isSwitchOn: result.switchState}); + }); + return true; + + case 'fetch-new-data': + chrome.storage.local.get('videoId', async function(result) { + const videoId = result.videoId; + const apiKey = "AIzaSyA0r8em0ndGCnx6vZu1Tv6T0iyLW4nB1jI"; // Replace with your YouTube Data API key + try { + const videoInfo = await getVideoInfo(apiKey, videoId); + const channelId = videoInfo.snippet.channelId; + const channelInfo = await getChannelInfo(apiKey, channelId); + + chrome.tabs.query({active: true, currentWindow: true}, function(tabs) { + //console.log("Sending message to content.js"); + chrome.tabs.sendMessage(tabs[0].id, { + action: 'append-channel-info', + channelName: channelInfo.channelName, + uploadTime: new Date(videoInfo.snippet.publishedAt).toLocaleString(), + videoCount: channelInfo.videoCount, + customUrl: channelInfo.customUrl }); - } catch (error) { - console.error(error); - } - }); - return true; + }); + } catch (error) { + console.error(error); + } + }); + return true; } }); @@ -377,6 +384,7 @@ async function getChannelInfo(apiKey, channelId) { const videoCount = channel.statistics.videoCount; const customUrl = channel.snippet.customUrl; - return { channelName, uploadTime, videoCount, customUrl };} + return { channelName, uploadTime, videoCount, customUrl }; +} /*-----# UNINSTALL URL-----------------------------------*/ chrome.runtime.setUninstallURL('https://improvedtube.com/uninstalled'); From d8088f9440010c2f8c2cb16e48eedf1c1f2b471a Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 16:56:39 -0500 Subject: [PATCH 10/13] Use SVG as icon for All Videos and More Details Link. Adjust the css for the Details area. Modularize the checkSwitchStateAndFetchData() function to DRY code --- js&css/extension/content.js | 125 ++++++++++++++++++++++-------------- 1 file changed, 78 insertions(+), 47 deletions(-) diff --git a/js&css/extension/content.js b/js&css/extension/content.js index 9a6fef847..6c0060cb3 100644 --- a/js&css/extension/content.js +++ b/js&css/extension/content.js @@ -9,94 +9,126 @@ function getVideoIdFromUrl() { function sendVideoIdToBackground() { const videoId = getVideoIdFromUrl(); if (videoId) { - console.log('Sending video ID to background:', videoId); + //console.log('Sending video ID to background:', videoId); chrome.runtime.sendMessage({ action: 'store-video-id', videoId: videoId }, function(response) { - console.log('Response from background:', response); + //console.log('Response from background:', response); }); } } function createChannelInfo(channelName, uploadTime, videoCount, customUrl) { - console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log + //console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log const container = document.createElement('div'); container.className = 'channel-info'; - // Add a line break after the upload time - const lineBreak = document.createElement('br'); + const channelInfoContainer = document.createElement('div'); if (uploadTime) { const uploadTimeElement = document.createElement('span'); uploadTimeElement.className = 'upload-time'; uploadTimeElement.textContent = `${uploadTime}`; - container.appendChild(uploadTimeElement); - container.appendChild(lineBreak); + channelInfoContainer.appendChild(uploadTimeElement); + container.appendChild(channelInfoContainer); } - - const allVideosLink = document.createElement('a'); - allVideosLink.className = 'all-videos-link'; - allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; - allVideosLink.textContent = 'All videos'; - container.appendChild(allVideosLink); - - if (videoCount) { + if (videoCount) { const videoCountElement = document.createElement('span'); videoCountElement.className = 'video-count'; videoCountElement.textContent = ` (${videoCount} videos)`; - container.appendChild(videoCountElement); - container.appendChild(lineBreak); + channelInfoContainer.appendChild(videoCountElement); + container.appendChild(channelInfoContainer); } - const viewDataLink = document.createElement('a'); - viewDataLink.className = 'view-data-link'; - viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; - viewDataLink.textContent = 'View video data'; - container.appendChild(viewDataLink); + const allVideosLink = document.createElement('a'); + allVideosLink.className = 'all-videos-link'; + allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; + allVideosLink.title = 'All Videos'; + + // Inline SVG + allVideosLink.innerHTML = ` + + + + `; + container.appendChild(allVideosLink); + + const viewDataLink = document.createElement('a'); + viewDataLink.className = 'view-data-link'; + viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; + // Tooltip text using `title` + viewDataLink.title = 'View detailed video data'; + + // Information SVG Icon + viewDataLink.innerHTML = ` + + + + `; + container.appendChild(viewDataLink); // Add custom CSS styles const style = document.createElement('style'); style.textContent = ` .channel-info { - background-color: #f9f9f9; - border: 1px solid #ddd; - padding: 10px; - margin-top: 10px; - margin-left: 10px; - margin-right: 10px; - border-radius: 5px; + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 2px; + align-items: center; + margin-right: 2px; } .channel-info .channel-name { font-weight: bold; display: block; margin-bottom: 5px; } - .channel-info .upload-time { + .channel-info .upload-time, .channel-info .video-count { color: #555; display: block; margin-bottom: 5px; + width: 80px; + font-size: 13px; + font-weight: bold; } .channel-info .all-videos-link, .channel-info .view-data-link { - color: #1a73e8; - text-decoration: none; - margin-right: 10px; + font-family: 'Roboto', Arial, sans-serif; + font-size: 13px; + font-weight: bold; + border: none; + border-radius: 20px; + cursor: pointer; + padding: 8px 8px; + transition: background-color 0.3s ease, transform 0.2s ease; } .channel-info .all-videos-link:hover, .channel-info .view-data-link:hover { text-decoration: underline; } - .channel-info .video-count { - color: #333; - display: block; - margin-top: 5px; - } `; document.head.appendChild(style); return container; } +// Check if the switch is on and fetch new data. Otherwise, remove the details block +function checkSwitchStateAndFetchData() { + chrome.runtime.sendMessage({ action: 'check-switch-state' }, function(response) { + if (response.isSwitchOn) { + chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + } else { + // Remove existing video details if the switch is off + const targetElement = document.querySelector('.channel-info') + if (targetElement) { + targetElement.remove(); + } + } + }); +} + document.addEventListener('DOMContentLoaded', function() { sendVideoIdToBackground() + //console.log('Content script loaded'); + let previousVideoId = getVideoIdFromUrl(); // Listen for changes in the video URL and send the updated video ID @@ -104,26 +136,25 @@ document.addEventListener('DOMContentLoaded', function() { const currentVideoId = getVideoIdFromUrl(); if (currentVideoId !== previousVideoId) { previousVideoId = currentVideoId; + sendVideoIdToBackground(); - // Check if the switch is on and fetch new-data - chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + checkSwitchStateAndFetchData(); } - }) + }); observer.observe(document.body, { childList: true, subtree: true }); // Listen for visibility changes to handle page navigation document.addEventListener('visibilitychange', function() { if (document.visibilityState === 'visible') { - // Check if the switch is on and re-fetch data if the user returns to the video page - chrome.runtime.sendMessage({ action: 'fetch-new-data' }); + checkSwitchStateAndFetchData(); } }); chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { - console.log('Received message from background:', message); // Debugging log + //console.log('Received message from background:', message); // Debugging log if (message.action === 'append-channel-info') { - console.log('Switch on Details mode'); + //console.log('Switch on Details mode'); const {channelName, uploadTime, videoCount, customUrl} = message; const channelInfo = createChannelInfo(channelName, uploadTime, videoCount, customUrl); const targetElement = document.querySelector('ytd-video-owner-renderer.style-scope.ytd-watch-metadata'); @@ -138,9 +169,9 @@ document.addEventListener('DOMContentLoaded', function() { console.error('Target element not found'); } } else if (message.action === 'remove-channel-info') { - console.log('Switch off Details mode'); + //console.log('Switch off Details mode'); const targetElement = document.querySelector('.channel-info'); - console.log(targetElement) + if(targetElement) { targetElement.remove(); } else { From 853cf449a71dd2e5bb33eb41e2e64ef78fbd1b5c Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 21:21:24 -0500 Subject: [PATCH 11/13] Add styles for Details area --- js&css/extension/www.youtube.com/styles.css | 40 +++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/js&css/extension/www.youtube.com/styles.css b/js&css/extension/www.youtube.com/styles.css index 1ecc02214..d065128a6 100644 --- a/js&css/extension/www.youtube.com/styles.css +++ b/js&css/extension/www.youtube.com/styles.css @@ -30,6 +30,7 @@ html {overflow-x: hidden !important} 6.0 Channel 6.1 "Play all" button 6.2 Featured content + 6.3 Details 7.0 Shortcuts 8.0 Settings 8.1 ImprovedTube icon on YouTube @@ -312,6 +313,45 @@ html[it-channel-hide-featured-content=true] #secondary ytd-browse-secondary-cont padding: 0; } +/*------------------------------------------------------------------------------ +6.3 Details +------------------------------------------------------------------------------*/ +.channel-info { + display: flex; + flex-direction: row; + justify-content: space-between; + gap: 2px; + align-items: center; + margin-right: 2px; +} +.channel-info .channel-name { + font-weight: bold; + display: block; + margin-bottom: 5px; +} +.channel-info .upload-time, .channel-info .video-count { + color: #555; + display: block; + margin-bottom: 5px; + width: 80px; + font-size: 13px; + font-weight: bold; +} +.channel-info .all-videos-link, +.channel-info .view-data-link { + font-family: 'Roboto', Arial, sans-serif; + font-size: 13px; + font-weight: bold; + border: none; + border-radius: 20px; + cursor: pointer; + padding: 8px 8px; + transition: background-color 0.3s ease, transform 0.2s ease; +} +.channel-info .all-videos-link:hover, +.channel-info .view-data-link:hover { + text-decoration: underline; +} /*------------------------------------------------------------------------------ 7.0 SHORTCUTS From 6a3760db53b88b949b36e746b7093edb1f3395a1 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 21:29:53 -0500 Subject: [PATCH 12/13] - Create a new function for ImprovedTub object that will handle manipulating the UI of Details area - Change the way of creating SVG to avoid CORS error - Move css styles to extension/styles.css - Use MutationObserver listens for DOM changes (e.g., when YouTube injects ytd-video-owner-renderer), ensuring the element is detected as soon as it's available. - To manipulate the UI, it listens to message from content.js to decide whether or not to display the Details area --- js&css/web-accessible/init.js | 1 + .../web-accessible/www.youtube.com/channel.js | 111 ++++++++++++++++++ 2 files changed, 112 insertions(+) diff --git a/js&css/web-accessible/init.js b/js&css/web-accessible/init.js index ca6596ca6..013364405 100644 --- a/js&css/web-accessible/init.js +++ b/js&css/web-accessible/init.js @@ -94,6 +94,7 @@ ImprovedTube.init = function () { this.myColors(); this.YouTubeExperiments(); this.channelCompactTheme(); + this.channelDetails(); if (ImprovedTube.elements.player && ImprovedTube.elements.player.setPlaybackRate) { ImprovedTube.videoPageUpdate(); diff --git a/js&css/web-accessible/www.youtube.com/channel.js b/js&css/web-accessible/www.youtube.com/channel.js index a2fc34a42..6b2e12a47 100644 --- a/js&css/web-accessible/www.youtube.com/channel.js +++ b/js&css/web-accessible/www.youtube.com/channel.js @@ -144,3 +144,114 @@ ImprovedTube.channelCompactTheme = function () { compact.styles = [] } } + +/*------------------------------------------------------------------------------ +4.6.4 Details +------------------------------------------------------------------------------*/ +ImprovedTube.channelDetails = function () { + function createChannelInfo(channelName, uploadTime, videoCount, customUrl) { + //console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log + const container = document.createElement('div'); + container.className = 'channel-info'; + + const channelInfoContainer = document.createElement('div'); + + if (uploadTime) { + const uploadTimeElement = document.createElement('span'); + uploadTimeElement.className = 'upload-time'; + uploadTimeElement.textContent = `${uploadTime}`; + channelInfoContainer.appendChild(uploadTimeElement); + container.appendChild(channelInfoContainer); + } + if (videoCount) { + const videoCountElement = document.createElement('span'); + videoCountElement.className = 'video-count'; + videoCountElement.textContent = ` (${videoCount} videos)`; + channelInfoContainer.appendChild(videoCountElement); + container.appendChild(channelInfoContainer); + } + + // All Videos Link + const allVideosLink = document.createElement('a'); + allVideosLink.className = 'all-videos-link'; + allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; + allVideosLink.title = 'All Videos'; + + const svgElement = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgElement.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgElement.setAttribute('viewBox', '0 0 24 24'); + svgElement.setAttribute('width', '24px'); + svgElement.setAttribute('height', '24px'); + svgElement.setAttribute('fill', 'gray'); + container.appendChild(allVideosLink); + + const pathElement = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + pathElement.setAttribute('d', 'M4 4h6v6H4V4zm0 10h6v6H4v-6zm10-10h6v6h-6V4zm0 10h6v6h-6v-6z'); + svgElement.appendChild(pathElement); + + allVideosLink.appendChild(svgElement); + container.appendChild(allVideosLink); + + // View Data Link + const viewDataLink = document.createElement('a'); + viewDataLink.className = 'view-data-link'; + viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; + // Tooltip text using `title` + viewDataLink.title = 'View detailed video data'; + + const svgElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'svg'); + svgElement2.setAttribute('xmlns', 'http://www.w3.org/2000/svg'); + svgElement2.setAttribute('viewBox', '0 0 24 24'); + svgElement2.setAttribute('width', '24px'); + svgElement2.setAttribute('height', '24px'); + svgElement2.setAttribute('fill', 'gray'); + container.appendChild(viewDataLink); + + const pathElement2 = document.createElementNS('http://www.w3.org/2000/svg', 'path'); + pathElement2.setAttribute('d', 'M11 7h2V5h-2v2zm1 14c-5.52 0-10-4.48-10-10S6.48 1 12 1s10 4.48 10 10-4.48 10-10 10zm-1-5h2v-6h-2v6z'); + svgElement2.appendChild(pathElement2); + + viewDataLink.appendChild(svgElement2); + container.appendChild(viewDataLink); + + // CSS for this Detail area is added in extension/styles.css + return container; + } + + document.addEventListener('DOMContentLoaded', function() { + // Listen for messages from the content.js script + window.addEventListener('message', (event) => { + // Only process messages with the expected type + if (event.source === window && event.data.type === 'CHANNEL_INFO') { + const { channelName, uploadTime, videoCount, customUrl, switchState } = event.data; + + const observer = new MutationObserver((mutationsList, observer) => { + const targetElement = document.querySelector('ytd-video-owner-renderer.style-scope.ytd-watch-metadata'); + + if (targetElement) { + // Stop observing once the target is found + observer.disconnect(); + + if(switchState) { + const channelInfo = createChannelInfo(channelName, uploadTime, videoCount, customUrl); + + // Removing existing channel info if present + const existingChannelInfo = targetElement.querySelector('.channel-info'); + if (existingChannelInfo) { + existingChannelInfo.remove(); + } + targetElement.appendChild(channelInfo); + } else { + targetElement.remove(); + } + } else { + console.log('Target element not found'); + } + }); + + // Start observing the body for child node changes + observer.observe(document.body, { childList: true, subtree: true }); + } + }); + }); +} \ No newline at end of file From b4e3d716ab343c437171572595b4e84c702fb960 Mon Sep 17 00:00:00 2001 From: Anh Chien Vu Date: Thu, 5 Dec 2024 21:33:11 -0500 Subject: [PATCH 13/13] - Moving function createChannelInfo() and all manipulation of Details area to channel.js under web-accessible folder. - Check the switch and fetch data immediately after the DOM is loaded - Passing video information to channel.js file by using window.PostMessage() functtion --- js&css/extension/content.js | 140 ++++++------------------------------ 1 file changed, 23 insertions(+), 117 deletions(-) diff --git a/js&css/extension/content.js b/js&css/extension/content.js index 6c0060cb3..5d824aaa1 100644 --- a/js&css/extension/content.js +++ b/js&css/extension/content.js @@ -16,100 +16,6 @@ function sendVideoIdToBackground() { } } -function createChannelInfo(channelName, uploadTime, videoCount, customUrl) { - //console.log('Creating channel info:', channelName, uploadTime, videoCount, customUrl); // Debugging log - const container = document.createElement('div'); - container.className = 'channel-info'; - - const channelInfoContainer = document.createElement('div'); - - if (uploadTime) { - const uploadTimeElement = document.createElement('span'); - uploadTimeElement.className = 'upload-time'; - uploadTimeElement.textContent = `${uploadTime}`; - channelInfoContainer.appendChild(uploadTimeElement); - container.appendChild(channelInfoContainer); - } - if (videoCount) { - const videoCountElement = document.createElement('span'); - videoCountElement.className = 'video-count'; - videoCountElement.textContent = ` (${videoCount} videos)`; - channelInfoContainer.appendChild(videoCountElement); - container.appendChild(channelInfoContainer); - } - - const allVideosLink = document.createElement('a'); - allVideosLink.className = 'all-videos-link'; - allVideosLink.href = `https://www.youtube.com/${customUrl}/videos`; - allVideosLink.title = 'All Videos'; - - // Inline SVG - allVideosLink.innerHTML = ` - - - - `; - container.appendChild(allVideosLink); - - const viewDataLink = document.createElement('a'); - viewDataLink.className = 'view-data-link'; - viewDataLink.href = 'https://ytlarge.com/youtube/video-data-viewer/'; - // Tooltip text using `title` - viewDataLink.title = 'View detailed video data'; - - // Information SVG Icon - viewDataLink.innerHTML = ` - - - - `; - container.appendChild(viewDataLink); - - // Add custom CSS styles - const style = document.createElement('style'); - style.textContent = ` - .channel-info { - display: flex; - flex-direction: row; - justify-content: space-between; - gap: 2px; - align-items: center; - margin-right: 2px; - } - .channel-info .channel-name { - font-weight: bold; - display: block; - margin-bottom: 5px; - } - .channel-info .upload-time, .channel-info .video-count { - color: #555; - display: block; - margin-bottom: 5px; - width: 80px; - font-size: 13px; - font-weight: bold; - } - .channel-info .all-videos-link, - .channel-info .view-data-link { - font-family: 'Roboto', Arial, sans-serif; - font-size: 13px; - font-weight: bold; - border: none; - border-radius: 20px; - cursor: pointer; - padding: 8px 8px; - transition: background-color 0.3s ease, transform 0.2s ease; - } - .channel-info .all-videos-link:hover, - .channel-info .view-data-link:hover { - text-decoration: underline; - } - `; - document.head.appendChild(style); - - return container; -} - // Check if the switch is on and fetch new data. Otherwise, remove the details block function checkSwitchStateAndFetchData() { chrome.runtime.sendMessage({ action: 'check-switch-state' }, function(response) { @@ -126,8 +32,10 @@ function checkSwitchStateAndFetchData() { } document.addEventListener('DOMContentLoaded', function() { + + // After DOM is loaded, check the switch state and fetch data even if the video ID is the same + checkSwitchStateAndFetchData(); sendVideoIdToBackground() - //console.log('Content script loaded'); let previousVideoId = getVideoIdFromUrl(); @@ -151,32 +59,30 @@ document.addEventListener('DOMContentLoaded', function() { } }); + // Listen for messages from the background.js script chrome.runtime.onMessage.addListener(function (message, sender, sendResponse) { - //console.log('Received message from background:', message); // Debugging log if (message.action === 'append-channel-info') { - //console.log('Switch on Details mode'); const {channelName, uploadTime, videoCount, customUrl} = message; - const channelInfo = createChannelInfo(channelName, uploadTime, videoCount, customUrl); - const targetElement = document.querySelector('ytd-video-owner-renderer.style-scope.ytd-watch-metadata'); - if (targetElement) { - // Removing existing channel info if present - const existingChannelInfo = targetElement.querySelector('.channel-info'); - if(existingChannelInfo) { - existingChannelInfo.remove(); - } - targetElement.appendChild(channelInfo); - } else { - console.error('Target element not found'); - } - } else if (message.action === 'remove-channel-info') { - //console.log('Switch off Details mode'); - const targetElement = document.querySelector('.channel-info'); - if(targetElement) { - targetElement.remove(); - } else { - console.error('Channel info element not found'); - } + // Forward the message to the web-accessible/www.youtube.com/channel.js script + window.postMessage( + { + type: 'CHANNEL_INFO', + channelName, + uploadTime, + videoCount, + customUrl, + switchState: true + }, + ); + } else if (message.action === 'remove-channel-info') { + // Forward the message to the web-accessible/www.youtube.com/channel.js script + window.postMessage( + { + type: 'CHANNEL_INFO', + switchState: false + }, + ); } }); }) \ No newline at end of file