mirror of
https://github.com/ilyhalight/voice-over-translation.git
synced 2026-10-19 07:43:33 +00:00
refactor: cleanup duplicates and dead code with fallow suggests
This commit is contained in:
parent
a0502a5587
commit
2ec15a71cf
69 changed files with 356 additions and 452 deletions
19
.fallowrc.jsonc
Normal file
19
.fallowrc.jsonc
Normal file
|
|
@ -0,0 +1,19 @@
|
|||
{
|
||||
"$schema": "https://raw.githubusercontent.com/fallow-rs/fallow/main/schema.json",
|
||||
"ignorePatterns": [
|
||||
".storybook/**",
|
||||
// bundle
|
||||
"dist/**",
|
||||
"dist-ext/**",
|
||||
".output/**",
|
||||
// autogenerated file
|
||||
"./src/types/localization.ts"
|
||||
],
|
||||
"ignoreDependencies": ["@storybook/addon-a11y", "@storybook/addon-docs"],
|
||||
"dynamicallyLoaded": ["src/extension/**/*.ts", "src/shims/nodeCrypto.ts"],
|
||||
"rules": {
|
||||
"unused-export": "warn",
|
||||
// many false-positive detections with this rule
|
||||
"unused-class-member": "off"
|
||||
}
|
||||
}
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
|
|
@ -158,3 +158,4 @@ dist/*.proxy.user.js
|
|||
|
||||
*storybook.log
|
||||
storybook-static
|
||||
.fallow/
|
||||
|
|
|
|||
|
|
@ -22,7 +22,6 @@ function isSolidPlugin(plugin: PluginOption): boolean {
|
|||
const config: StorybookConfig = {
|
||||
stories: ["../src/**/*.stories.@(js|jsx|mjs|ts|tsx)"],
|
||||
addons: [
|
||||
"@chromatic-com/storybook",
|
||||
"@storybook/addon-vitest",
|
||||
"@storybook/addon-a11y",
|
||||
"@storybook/addon-docs",
|
||||
|
|
|
|||
37
bun.lock
37
bun.lock
|
|
@ -18,7 +18,6 @@
|
|||
},
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.14",
|
||||
"@chromatic-com/storybook": "latest",
|
||||
"@crxjs/vite-plugin": "^2.7.1",
|
||||
"@storybook/addon-a11y": "^10.6.0",
|
||||
"@storybook/addon-docs": "^10.6.0",
|
||||
|
|
@ -106,8 +105,6 @@
|
|||
|
||||
"@bufbuild/protobuf": ["@bufbuild/protobuf@2.15.0", "", {}, "sha512-DAheWUkVr/SJTWCc+lg9dhY0eN4SaWlf4+bG1KzHeXbnqt0AfB/NX0Z+VunGlM1ki1B4zVvye27MpKh/svySUA=="],
|
||||
|
||||
"@chromatic-com/storybook": ["@chromatic-com/storybook@5.3.1", "", { "dependencies": { "@neoconfetti/react": "^1.0.0", "chromatic": "^18.0.1", "jsonfile": "^6.1.0", "strip-ansi": "^7.1.0" }, "peerDependencies": { "storybook": "^0.0.0-0 || ^10.1.0 || ^10.1.0-0 || ^10.2.0-0 || ^10.3.0-0 || ^10.4.0-0 || ^10.5.0-0 || ^10.6.0-0 || ^11.0.0-0" } }, "sha512-eUcbR9CddVmKAnjOiBCOz5rNdT096jiXIjR+qaWbUuTlgRdpc9yjTrk4/hiPAAykZrPYVNJN11p6rJgW9KNpKg=="],
|
||||
|
||||
"@crxjs/vite-plugin": ["@crxjs/vite-plugin@2.7.1", "", { "dependencies": { "@webcomponents/custom-elements": "^1.5.0", "acorn-walk": "^8.3.5", "convert-source-map": "^2.0.0", "debug": "^4.4.3", "es-module-lexer": "^0.10.0", "fs-extra": "^11.3.5", "jsesc": "^3.0.2", "magic-string": "^0.30.12", "node-html-parser": "^7.1.0", "pathe": "^2.0.1", "picocolors": "^1.1.1", "rollup": "2.80.0", "rxjs": "7.5.7", "tinyglobby": "^0.2.17" }, "peerDependencies": { "vite": "^3.0.0 || ^4.0.0 || ^5.0.0 || ^6.0.0 || ^7.0.0 || ^8.0.0" } }, "sha512-mn77Lc1ilIvbO7ZqijjNy+AnRgc8srkfHOtV6HrGrYZgRe8YI3aIcSjHJCU5Kq75SejEZGrdAoHdn3gBGeKpEA=="],
|
||||
|
||||
"@devicefarmer/adbkit": ["@devicefarmer/adbkit@3.3.9", "", { "dependencies": { "@devicefarmer/adbkit-logcat": "^2.1.2", "@devicefarmer/adbkit-monkey": "~1.2.1", "bluebird": "~3.7", "commander": "^9.1.0", "debug": "~4.3.1", "node-forge": "^1.3.1", "split": "~1.0.1" }, "bin": { "adbkit": "bin/adbkit" } }, "sha512-AYxk5G/b2BPEfubcuxVEZEOpsLf0isqrPNJnnwAlUjZAiLdFg/uvLNRDuBhsC/Ar62X+rUhVlvlL+tKp9HCjfQ=="],
|
||||
|
|
@ -258,8 +255,6 @@
|
|||
|
||||
"@napi-rs/wasm-runtime": ["@napi-rs/wasm-runtime@1.2.4", "", { "dependencies": { "@tybys/wasm-util": "^0.10.3" }, "peerDependencies": { "@emnapi/core": "^1.7.1 || ^2.0.0-alpha.4", "@emnapi/runtime": "^1.7.1 || ^2.0.0-alpha.4" } }, "sha512-AJxoUD2/15ESHbvpcyjU274nsAPLuOtPHCk0vKJM5pj//Fg/B1FXNWjPnXTT9PymCYYiHo4zPj0ZomXBKhoy7g=="],
|
||||
|
||||
"@neoconfetti/react": ["@neoconfetti/react@1.0.0", "", {}, "sha512-klcSooChXXOzIm+SE5IISIAn3bYzYfPjbX7D7HoqZL84oAfgREeSg5vSIaSFH+DaGzzvImTyWe1OyrJ67vik4A=="],
|
||||
|
||||
"@oxc-parser/binding-android-arm-eabi": ["@oxc-parser/binding-android-arm-eabi@0.127.0", "", { "os": "android", "cpu": "arm" }, "sha512-0LC7ye4hvqbIKxAzThzvswgHLFu2AURKzYLeSVvLdu2TBOYWQDmHnTqPLeA597BcUCxiLqLsS4CJ5uoI5WYWCQ=="],
|
||||
|
||||
"@oxc-parser/binding-android-arm64": ["@oxc-parser/binding-android-arm64@0.127.0", "", { "os": "android", "cpu": "arm64" }, "sha512-b5jtVTH6AU5CJXHNdj7Jj9IEiR9yVjjnwHzPJhGyHGPdcsZSzBCkS9GBbV33niRMvKthDwQRFRJfI4a+k4PvYg=="],
|
||||
|
|
@ -494,8 +489,6 @@
|
|||
|
||||
"@volar/typescript": ["@volar/typescript@2.4.28", "", { "dependencies": { "@volar/language-core": "2.4.28", "path-browserify": "^1.0.1", "vscode-uri": "^3.0.8" } }, "sha512-Ja6yvWrbis2QtN4ClAKreeUZPVYMARDYZl9LMEv1iQ1QdepB6wn0jTRxA9MftYmYa4DQ4k/DaSZpFPUfxl8giw=="],
|
||||
|
||||
|
||||
|
||||
"@vot.js/core": ["@vot.js/core@3.1.2", "", { "dependencies": { "@vot.js/shared": "^3.1.2" }, "peerDependencies": { "typescript": "^6.0.0" } }, "sha512-Hi+csDJA4+E268lsbOQBBLgWYwjOeMH7p0kg5Atwk6ZnmMYwPZIozvKtqGaRFcVj5zg1VzrJZ8cDC6QThQ0Gpg=="],
|
||||
|
||||
"@vot.js/ext": ["@vot.js/ext@3.1.2", "", { "dependencies": { "@vot.js/core": "^3.1.2", "@vot.js/shared": "^3.1.2" }, "peerDependencies": { "typescript": "^6.0.0" } }, "sha512-hfdBnvgT8JYkWnlZ9hli6wUXJZKWlyGz7cNWuCiBSD/Sfs7ghZ7uUungi2tRjlp7L/iMI3FAvR7z/8iVrRK7sg=="],
|
||||
|
|
@ -526,7 +519,7 @@
|
|||
|
||||
"ansi-align": ["ansi-align@3.0.1", "", { "dependencies": { "string-width": "^4.1.0" } }, "sha512-IOfwwBF5iczOjp/WeY4YxyjqAFMQoZufdQWDd19SEExbVLNXqvpzSJ/M7Za4/sCPmQ0+GRquoA7bGcINcxew6w=="],
|
||||
|
||||
"ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
|
||||
"ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"ansi-styles": ["ansi-styles@7.0.0", "", {}, "sha512-kKvt3m4uwzqL0wlkPd09CmljPJGOZZ4D0fP65sqFSvPkMRKhNi+74MgIJ5QxE6SxqB4t4KyUFGg8+n5zjo6hew=="],
|
||||
|
||||
|
|
@ -602,8 +595,6 @@
|
|||
|
||||
"chokidar": ["chokidar@5.0.0", "", { "dependencies": { "readdirp": "^5.0.0" } }, "sha512-TQMmc3w+5AxjpL8iIiwebF73dRDF4fBIieAqGn9RGCWaEVwQ6Fb2cGe31Yns0RRIzii5goJ1Y7xbMwo1TxMplw=="],
|
||||
|
||||
"chromatic": ["chromatic@18.9.4", "", { "dependencies": { "semver": "^7.3.5" }, "peerDependencies": { "@chromatic-com/cypress": "^0.*.* || ^1.0.0", "@chromatic-com/playwright": "^0.*.* || ^1.0.0", "@chromatic-com/vitest": "^0.*.* || ^1.0.0" }, "optionalPeers": ["@chromatic-com/cypress", "@chromatic-com/playwright", "@chromatic-com/vitest"], "bin": { "chroma": "dist/bin.cjs", "chromatic": "dist/bin.cjs", "chromatic-cli": "dist/bin.cjs" } }, "sha512-+/socRBceUAk19XwvnnJTgkgw6YJ5Fh58HoWM5ipiLwTnZS82r/cCM164ROKYVajeuCeo3ROsH+6nVHAylin5g=="],
|
||||
|
||||
"chrome-launcher": ["chrome-launcher@1.2.0", "", { "dependencies": { "@types/node": "*", "escape-string-regexp": "^4.0.0", "is-wsl": "^2.2.0", "lighthouse-logger": "^2.0.1" }, "bin": { "print-chrome-path": "bin/print-chrome-path.cjs" } }, "sha512-JbuGuBNss258bvGil7FT4HKdC3SC2K7UAEUqiPy3ACS3Yxo3hAW6bvFpCu2HsIJLgTqxgEX6BkujvzZfLpUD0Q=="],
|
||||
|
||||
"cli-boxes": ["cli-boxes@3.0.0", "", {}, "sha512-/lzGpEWL/8PfI0BmBOPRwp0c/wFNX1RdUML3jK/RcSBA9T8mZDdQpqYBKtCFTOfQbwPqWEOpjqW+Fnayc0969g=="],
|
||||
|
|
@ -1174,7 +1165,7 @@
|
|||
|
||||
"string_decoder": ["string_decoder@1.1.1", "", { "dependencies": { "safe-buffer": "~5.1.0" } }, "sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg=="],
|
||||
|
||||
"strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
"strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"strip-bom": ["strip-bom@5.0.0", "", {}, "sha512-p+byADHF7SzEcVnLvc/r3uognM1hUhObuHXxJcgLCfD194XAkaLbjq3Wzb0N5G2tgIjH0dgT708Z51QxMeu60A=="],
|
||||
|
||||
|
|
@ -1354,12 +1345,8 @@
|
|||
|
||||
"boxen/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"cliui/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"cliui/wrap-ansi": ["wrap-ansi@7.0.0", "", { "dependencies": { "ansi-styles": "^4.0.0", "string-width": "^4.1.0", "strip-ansi": "^6.0.0" } }, "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q=="],
|
||||
|
||||
"columnify/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"config-chain/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
||||
"cross-spawn/which": ["which@2.0.2", "", { "dependencies": { "isexe": "^2.0.0" }, "bin": { "node-which": "./bin/node-which" } }, "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA=="],
|
||||
|
|
@ -1390,8 +1377,6 @@
|
|||
|
||||
"jws/safe-buffer": ["safe-buffer@5.2.1", "", {}, "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="],
|
||||
|
||||
"pretty-format/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"pretty-format/ansi-styles": ["ansi-styles@5.2.0", "", {}, "sha512-Cxwpt2SfTzTtXcfOlzGEee8O+c+MmUgGrNiBcXnuWxuFJHe6a5Hz7qwhwe5OgaSYI0IJvkLqWX1ASG+cJOkEiA=="],
|
||||
|
||||
"rc/ini": ["ini@1.3.8", "", {}, "sha512-JV/yugV2uzW5iMRSiZAyDtQd+nxtUnjeLt0acNdw98kKLrvuRVyB80tsREOE7yvGVgalhZ6RNXCmEHkUKBKxew=="],
|
||||
|
|
@ -1400,8 +1385,6 @@
|
|||
|
||||
"rolldown/@oxc-project/types": ["@oxc-project/types@0.150.0", "", {}, "sha512-rDS5/31E9HfPl/CIzGrn0DOlvBbXFseQ5URJ9sYMfstbKLD/c6Gm9vmRzRGDdAXyOIL4zmO37lc9RIwYqVruZw=="],
|
||||
|
||||
"string-width/strip-ansi": ["strip-ansi@6.0.1", "", { "dependencies": { "ansi-regex": "^5.0.1" } }, "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A=="],
|
||||
|
||||
"thread-stream/real-require": ["real-require@1.0.0", "", {}, "sha512-P4nbQYQfePJxRSmY+v/KINxVucm4NF3p3s7pJveMTtom52FR4YGltUQLB8idDXwDDWW+eYrWDFbuzUnjoWHF7g=="],
|
||||
|
||||
"vitest/es-module-lexer": ["es-module-lexer@2.3.2", "", {}, "sha512-poHGpORABojJJucnV9KbOavETW8lBVnphkW77ER5/BQ5Fz7oXSoCNek7IH3vR5nRjdsEz926ibFYX8KtLQmdyw=="],
|
||||
|
|
@ -1416,6 +1399,8 @@
|
|||
|
||||
"wrap-ansi/string-width": ["string-width@7.2.0", "", { "dependencies": { "emoji-regex": "^10.3.0", "get-east-asian-width": "^1.0.0", "strip-ansi": "^7.1.0" } }, "sha512-tsaTIkKW9b4N+AEj+SVA+WhJzV7/zMhcSu78mLKWSk7cXMOSHsBKFWUs0fWwq8QyK3MgJBQRX6Gbi4kYbdvGkQ=="],
|
||||
|
||||
"wrap-ansi/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
|
||||
|
||||
"zip-dir/jszip": ["jszip@3.10.2", "", { "dependencies": { "lie": "~3.3.0", "pako": "~1.0.2", "readable-stream": "~2.3.6", "setimmediate": "^1.0.5" } }, "sha512-3l+rb15IOWtUhU0H5MFqES/T6Kh7abYwjosBey/vD6hDt8zoEffkSC5Ws5SGtgVw3gBx2NEbhTeSW1+kWkpyTQ=="],
|
||||
|
|
@ -1432,12 +1417,10 @@
|
|||
|
||||
"boxen/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"cliui/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
"boxen/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"cliui/wrap-ansi/ansi-styles": ["ansi-styles@4.3.0", "", { "dependencies": { "color-convert": "^2.0.1" } }, "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg=="],
|
||||
|
||||
"columnify/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"cross-spawn/which/isexe": ["isexe@2.0.0", "", {}, "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="],
|
||||
|
||||
"eslint/ajv/json-schema-traverse": ["json-schema-traverse@0.4.1", "", {}, "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="],
|
||||
|
|
@ -1446,14 +1429,20 @@
|
|||
|
||||
"fx-runner/which/isexe": ["isexe@3.1.5", "", {}, "sha512-6B3tLtFqtQS4ekarvLVMZ+X+VlvQekbe4taUkf/rhVO3d/h0M2rfARm/pXLcPEsjjMsFgrFgSrhQIxcSVrBz8w=="],
|
||||
|
||||
"string-width/strip-ansi/ansi-regex": ["ansi-regex@5.0.1", "", {}, "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="],
|
||||
|
||||
"web-ext/open/wsl-utils": ["wsl-utils@0.3.1", "", { "dependencies": { "is-wsl": "^3.1.0", "powershell-utils": "^0.1.0" } }, "sha512-g/eziiSUNBSsdDJtCLB8bdYEUMj4jR7AGeUo96p/3dTafgjHhpF4RiCFPiRILwjQoDXx5MqkBr4fwWtR3Ky4Wg=="],
|
||||
|
||||
"widest-line/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"widest-line/string-width/strip-ansi": ["strip-ansi@7.2.0", "", { "dependencies": { "ansi-regex": "^6.2.2" } }, "sha512-yDPMNjp4WyfYBkHnjIRLfca1i6KMyGCtsVgoKe/z1+6vukgaENdgGBZt+ZmKPc4gavvEZ5OgHfHdrazhgNyG7w=="],
|
||||
|
||||
"wrap-ansi/string-width/emoji-regex": ["emoji-regex@10.6.0", "", {}, "sha512-toUI84YS5YmxW219erniWD0CIVOo46xGKColeNQRgOzDorgBi1v4D71/OFzgD9GO2UGKIv1C3Sp8DAn0+j5w7A=="],
|
||||
|
||||
"wrap-ansi/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
|
||||
|
||||
"boxen/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
|
||||
|
||||
"web-ext/open/wsl-utils/is-wsl": ["is-wsl@3.1.1", "", { "dependencies": { "is-inside-container": "^1.0.0" } }, "sha512-e6rvdUCiQCAuumZslxRJWR/Doq4VpPR82kqclvcS0efgt430SlGIk05vdCN58+VrzgtIcfNODjozVielycD4Sw=="],
|
||||
|
||||
"widest-line/string-width/strip-ansi/ansi-regex": ["ansi-regex@6.3.0", "", {}, "sha512-WpDfL7NO6j7tH88IDBNVdUJxDh9nmCteAVW9dsep846XdwF4naCBK+/tGLX3KJgcpgMRXCFlTM2hKGoK9FsdrQ=="],
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -16,7 +16,6 @@
|
|||
],
|
||||
"devDependencies": {
|
||||
"@biomejs/biome": "^2.5.14",
|
||||
"@chromatic-com/storybook": "latest",
|
||||
"@crxjs/vite-plugin": "^2.7.1",
|
||||
"@storybook/addon-a11y": "^10.6.0",
|
||||
"@storybook/addon-docs": "^10.6.0",
|
||||
|
|
|
|||
|
|
@ -7,6 +7,7 @@ import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
|||
import type { ClientSession, SessionModule } from "@vot.js/shared/types/secure";
|
||||
import Chaimu from "chaimu/client";
|
||||
import { initAudioContext } from "chaimu/player";
|
||||
|
||||
import {
|
||||
minLongWaitingCount,
|
||||
proxyWorkerHostMode1,
|
||||
|
|
@ -23,10 +24,11 @@ import { createVideoLifecycleHost } from "./core/videoLifecycleHost";
|
|||
import { VOTVideoManager } from "./core/videoManager";
|
||||
import { localizationProvider, t } from "./localization/localizationProvider";
|
||||
import { Notifier } from "./notify";
|
||||
import type { ProcessedSubtitles } from "./subtitles/processor";
|
||||
import { SubtitlesWidget } from "./subtitles/widget";
|
||||
import type { ResponseLanguageSubtitles, StorageData } from "./types/storage";
|
||||
import type { ProcessedSubtitles } from "./types/subtitles";
|
||||
import type { OverlayMount } from "./types/uiManager";
|
||||
import type { VideoData } from "./types/videoHandler";
|
||||
import { UIManager } from "./ui/manager";
|
||||
import { isSameOverlayMount } from "./ui/mount";
|
||||
import { OverlayVisibilityController } from "./ui/overlayVisibilityController";
|
||||
|
|
@ -88,7 +90,6 @@ import {
|
|||
updateTranslation as updateTranslationImpl,
|
||||
validateAudioUrl as validateAudioUrlImpl,
|
||||
} from "./videoHandler/modules/translation";
|
||||
import type { VideoData } from "./videoHandler/shared";
|
||||
import {
|
||||
type ApplyVolumeLinkDeltaResult,
|
||||
applyVolumeLinkDelta,
|
||||
|
|
|
|||
|
|
@ -313,7 +313,7 @@ async function getPlayer(
|
|||
);
|
||||
}
|
||||
|
||||
export function createAudioChunkStream(
|
||||
function createAudioChunkStream(
|
||||
targetWindow: MseWindow,
|
||||
videoId: string,
|
||||
signal: AbortSignal,
|
||||
|
|
@ -842,7 +842,7 @@ async function handleTopRequest(
|
|||
).appendChild(iframe);
|
||||
}
|
||||
|
||||
export function initMseProxyHandler(): void {
|
||||
function initMseProxyHandler(): void {
|
||||
const pageWindow = globalThis as unknown as MseWindow;
|
||||
if (
|
||||
pageWindow[BOOT_KEY] ||
|
||||
|
|
|
|||
|
|
@ -205,7 +205,7 @@ export async function mintPagePoToken(
|
|||
}
|
||||
}
|
||||
|
||||
export function selectGvsPoTokenBinding(
|
||||
function selectGvsPoTokenBinding(
|
||||
videoId: string,
|
||||
options: {
|
||||
loggedIn: boolean;
|
||||
|
|
@ -234,6 +234,28 @@ function getConfigValue(config: YouTubeConfig, key: string): unknown {
|
|||
return config.get?.(key) ?? config.data_?.[key];
|
||||
}
|
||||
|
||||
function cloneInnertubeContext(
|
||||
config: YouTubeConfig,
|
||||
unavailableMessage: string,
|
||||
): {
|
||||
context: {
|
||||
client?: Record<string, unknown>;
|
||||
thirdParty?: Record<string, unknown>;
|
||||
};
|
||||
client: Record<string, unknown>;
|
||||
} {
|
||||
const rawContext = getConfigValue(config, "INNERTUBE_CONTEXT");
|
||||
if (!rawContext || typeof rawContext !== "object") {
|
||||
throw new Error(unavailableMessage);
|
||||
}
|
||||
const context = JSON.parse(JSON.stringify(rawContext)) as {
|
||||
client?: Record<string, unknown>;
|
||||
thirdParty?: Record<string, unknown>;
|
||||
};
|
||||
context.client ??= {};
|
||||
return { context, client: context.client };
|
||||
}
|
||||
|
||||
function buildContentPlaybackContext(
|
||||
signatureTimestamp: unknown,
|
||||
): Record<string, unknown> {
|
||||
|
|
@ -274,7 +296,7 @@ function findJsonValueEnd(source: string, start: number): number {
|
|||
// The page keeps its config in the ytcfg global, which a sandboxed userscript
|
||||
// realm cannot read. Both calling forms carry plain JSON, so the same inline
|
||||
// script that builds ytcfg can be replayed from its source text instead.
|
||||
export function parseYtcfgData(source: string): Record<string, unknown> {
|
||||
function parseYtcfgData(source: string): Record<string, unknown> {
|
||||
const data: Record<string, unknown> = {};
|
||||
const pattern = /ytcfg\s*\.\s*set\s*\(/g;
|
||||
const skipSpaces = (index: number) => {
|
||||
|
|
@ -334,7 +356,7 @@ function readYtcfgFromDocument(targetWindow: Window): Record<string, unknown> {
|
|||
return data;
|
||||
}
|
||||
|
||||
export async function resolveYtcfg(
|
||||
async function resolveYtcfg(
|
||||
targetWindow: WebAbrWindow,
|
||||
signal: AbortSignal,
|
||||
): Promise<YouTubeConfig> {
|
||||
|
|
@ -382,17 +404,10 @@ export function buildWebEmbeddedPlayerRequest(
|
|||
videoId: string,
|
||||
extractedSignatureTimestamp?: number,
|
||||
): Record<string, unknown> {
|
||||
const rawContext = getConfigValue(config, "INNERTUBE_CONTEXT");
|
||||
if (!rawContext || typeof rawContext !== "object") {
|
||||
throw new Error("Audio downloader. web_embedded context is unavailable");
|
||||
}
|
||||
|
||||
const context = JSON.parse(JSON.stringify(rawContext)) as {
|
||||
client?: Record<string, unknown>;
|
||||
thirdParty?: Record<string, unknown>;
|
||||
};
|
||||
context.client ??= {};
|
||||
const client = context.client;
|
||||
const { context, client } = cloneInnertubeContext(
|
||||
config,
|
||||
"Audio downloader. web_embedded context is unavailable",
|
||||
);
|
||||
client.clientName = "WEB_EMBEDDED_PLAYER";
|
||||
client.clientVersion =
|
||||
getConfigValue(config, "INNERTUBE_CLIENT_VERSION") ?? client.clientVersion;
|
||||
|
|
@ -963,7 +978,7 @@ function buildSolvedUrl(
|
|||
return url.toString();
|
||||
}
|
||||
|
||||
export async function* resolveWebEmbeddedFormatUrl(
|
||||
async function* resolveWebEmbeddedFormatUrl(
|
||||
targetWindow: WebAbrWindow,
|
||||
format: WebEmbeddedFormat,
|
||||
playerCode: () => Promise<string | undefined>,
|
||||
|
|
@ -1111,17 +1126,10 @@ export function buildWebPlayerRequest(
|
|||
videoId: string,
|
||||
extractedSignatureTimestamp?: number,
|
||||
): Record<string, unknown> {
|
||||
const rawContext = getConfigValue(config, "INNERTUBE_CONTEXT");
|
||||
if (!rawContext || typeof rawContext !== "object") {
|
||||
throw new Error("Audio downloader. web client context is unavailable");
|
||||
}
|
||||
|
||||
const context = JSON.parse(JSON.stringify(rawContext)) as {
|
||||
client?: Record<string, unknown>;
|
||||
thirdParty?: Record<string, unknown>;
|
||||
};
|
||||
context.client ??= {};
|
||||
const client = context.client;
|
||||
const { context, client } = cloneInnertubeContext(
|
||||
config,
|
||||
"Audio downloader. web client context is unavailable",
|
||||
);
|
||||
client.clientName = "WEB";
|
||||
client.clientVersion =
|
||||
getConfigValue(config, "INNERTUBE_CLIENT_VERSION") ?? client.clientVersion;
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@ const MESSAGE_TYPE = "get-audio-chunks-by-mse-in-main-world";
|
|||
export const STREAM_TIMEOUT_MS = 30 * 60_000;
|
||||
const MESSAGE_TIMEOUT_MS = 5 * 60_000;
|
||||
|
||||
export function parseAudioBridgeChunk(payload: unknown): AudioChunk {
|
||||
function parseAudioBridgeChunk(payload: unknown): AudioChunk {
|
||||
if (!payload || typeof payload !== "object" || !("buffer" in payload)) {
|
||||
throw new Error("Audio downloader. Invalid audio bridge chunk");
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,5 @@
|
|||
$margin: 0 !important
|
||||
);
|
||||
border: solid 1px var(--vot-border-color) !important;
|
||||
color: rgb(var(--vot-helper-theme));
|
||||
background-color: transparent;
|
||||
@include button.vot-button-layers(rgb(var(--vot-helper-theme)));
|
||||
@include button.vot-button-layer-hover(0.04);
|
||||
@include button.vot-button-layer-active(0.16);
|
||||
|
||||
&[disabled="true"],
|
||||
&[aria-disabled="true"] {
|
||||
background-color: transparent;
|
||||
color: rgba(var(--vot-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
cursor: initial;
|
||||
@include button.vot-button-layer-reset-disabled;
|
||||
}
|
||||
@include button.vot-button-theme-transparent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
import { createSignal, onCleanup } from "solid-js";
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { DownloadIcon } from "../Icons/DownloadIcon";
|
||||
import { startProgressAnimation } from "../Icons/ProgressIcon.stories";
|
||||
import { ProgressIconButton } from "./ProgressIconButton";
|
||||
|
||||
const meta = {
|
||||
|
|
@ -40,16 +41,11 @@ export const ProgressIconButtonChanging: Story = {
|
|||
onClick={() => {
|
||||
setShowProgress(true);
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
const nextProgress = Math.min(progress() + 0.1, 100);
|
||||
|
||||
setProgress(nextProgress);
|
||||
|
||||
if (nextProgress === 100) {
|
||||
window.clearInterval(intervalId);
|
||||
setShowProgress(false);
|
||||
}
|
||||
}, 10);
|
||||
const intervalId = startProgressAnimation(
|
||||
progress,
|
||||
setProgress,
|
||||
() => setShowProgress(false),
|
||||
);
|
||||
|
||||
onCleanup(() => window.clearInterval(intervalId));
|
||||
}}
|
||||
|
|
|
|||
|
|
@ -14,17 +14,5 @@
|
|||
$margin: 0 !important
|
||||
);
|
||||
border: none !important;
|
||||
color: rgb(var(--vot-helper-theme));
|
||||
background-color: transparent;
|
||||
@include button.vot-button-layers(rgb(var(--vot-helper-theme)));
|
||||
@include button.vot-button-layer-hover(0.04);
|
||||
@include button.vot-button-layer-active(0.16);
|
||||
|
||||
&[disabled="true"],
|
||||
&[aria-disabled="true"] {
|
||||
background-color: transparent;
|
||||
color: rgba(var(--vot-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
cursor: initial;
|
||||
@include button.vot-button-layer-reset-disabled;
|
||||
}
|
||||
@include button.vot-button-theme-transparent;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -82,3 +82,19 @@
|
|||
opacity: 0;
|
||||
}
|
||||
}
|
||||
|
||||
@mixin vot-button-theme-transparent {
|
||||
color: rgb(var(--vot-helper-theme));
|
||||
background-color: transparent;
|
||||
@include vot-button-layers(rgb(var(--vot-helper-theme)));
|
||||
@include vot-button-layer-hover(0.04);
|
||||
@include vot-button-layer-active(0.16);
|
||||
|
||||
&[disabled="true"],
|
||||
&[aria-disabled="true"] {
|
||||
background-color: transparent;
|
||||
color: rgba(var(--vot-onsurface-rgb, 0, 0, 0), 0.38);
|
||||
cursor: initial;
|
||||
@include vot-button-layer-reset-disabled;
|
||||
}
|
||||
}
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@ export type SliderLabelDescProps = {
|
|||
ref?: (element: HTMLElement) => void;
|
||||
};
|
||||
|
||||
// fallow-ignore-next-line unused-export
|
||||
export function SliderLabelDesc(props: SliderLabelDescProps): JSX.Element {
|
||||
return (
|
||||
<vot-block ref={props.ref} class="vot-slider-label__text-desc">
|
||||
|
|
|
|||
|
|
@ -1,7 +1,30 @@
|
|||
import { createSignal, onCleanup } from "solid-js";
|
||||
import { type Accessor, createSignal, onCleanup, type Setter } from "solid-js";
|
||||
|
||||
import type { Meta, StoryObj } from "storybook-solidjs-vite";
|
||||
import { ProgressIcon } from "./ProgressIcon";
|
||||
|
||||
const PROGRESS_STEP = 0.1;
|
||||
const PROGRESS_INTERVAL_MS = 10;
|
||||
|
||||
export function startProgressAnimation(
|
||||
progress: Accessor<number>,
|
||||
setProgress: Setter<number>,
|
||||
onComplete?: () => void,
|
||||
) {
|
||||
const intervalId = window.setInterval(() => {
|
||||
const nextProgress = Math.min(progress() + PROGRESS_STEP, 100);
|
||||
|
||||
setProgress(nextProgress);
|
||||
|
||||
if (nextProgress === 100) {
|
||||
window.clearInterval(intervalId);
|
||||
onComplete?.();
|
||||
}
|
||||
}, PROGRESS_INTERVAL_MS);
|
||||
|
||||
return intervalId;
|
||||
}
|
||||
|
||||
const meta = {
|
||||
component: ProgressIcon,
|
||||
render: (args) => (
|
||||
|
|
@ -40,15 +63,7 @@ export const ProgressIconChanging: Story = {
|
|||
render: () => {
|
||||
const [progress, setProgress] = createSignal(0);
|
||||
|
||||
const intervalId = window.setInterval(() => {
|
||||
const nextProgress = Math.min(progress() + 0.1, 100);
|
||||
|
||||
setProgress(nextProgress);
|
||||
|
||||
if (nextProgress === 100) {
|
||||
window.clearInterval(intervalId);
|
||||
}
|
||||
}, 10);
|
||||
const intervalId = startProgressAnimation(progress, setProgress);
|
||||
|
||||
onCleanup(() => window.clearInterval(intervalId));
|
||||
|
||||
|
|
|
|||
|
|
@ -197,6 +197,26 @@ export const SegmentedButtonPrimaryAction: Story = {
|
|||
},
|
||||
};
|
||||
|
||||
const playColumnTouchVoiceSelection = (canvasElement: HTMLElement) => {
|
||||
const translateButton = canvasElement.querySelector<HTMLElement>(
|
||||
".vot-translate-button",
|
||||
);
|
||||
expect(translateButton).not.toBeNull();
|
||||
if (!translateButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPrimaryPointerUp(translateButton, "touch");
|
||||
const popover =
|
||||
canvasElement.ownerDocument.querySelector<HTMLElement>(
|
||||
".vot-voice-popover",
|
||||
);
|
||||
return {
|
||||
translateButton,
|
||||
popover,
|
||||
};
|
||||
};
|
||||
|
||||
export const SegmentedButtonColumnTouchVoiceSelection: Story = {
|
||||
args: {
|
||||
direction: "column",
|
||||
|
|
@ -204,20 +224,7 @@ export const SegmentedButtonColumnTouchVoiceSelection: Story = {
|
|||
onTranslateClick: fn(),
|
||||
},
|
||||
play: ({ args, canvasElement }) => {
|
||||
const translateButton = canvasElement.querySelector<HTMLElement>(
|
||||
".vot-translate-button",
|
||||
);
|
||||
expect(translateButton).not.toBeNull();
|
||||
if (!translateButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPrimaryPointerUp(translateButton, "touch");
|
||||
const popover =
|
||||
canvasElement.ownerDocument.querySelector<HTMLElement>(
|
||||
".vot-voice-popover",
|
||||
);
|
||||
|
||||
const { popover } = playColumnTouchVoiceSelection(canvasElement);
|
||||
expect(args.onTranslateClick).not.toHaveBeenCalled();
|
||||
expect(popover?.hidden).toBe(false);
|
||||
},
|
||||
|
|
@ -231,20 +238,7 @@ export const SegmentedButtonColumnTouchError: Story = {
|
|||
status: "error",
|
||||
},
|
||||
play: ({ args, canvasElement }) => {
|
||||
const translateButton = canvasElement.querySelector<HTMLElement>(
|
||||
".vot-translate-button",
|
||||
);
|
||||
expect(translateButton).not.toBeNull();
|
||||
if (!translateButton) {
|
||||
return;
|
||||
}
|
||||
|
||||
dispatchPrimaryPointerUp(translateButton, "touch");
|
||||
const popover =
|
||||
canvasElement.ownerDocument.querySelector<HTMLElement>(
|
||||
".vot-voice-popover",
|
||||
);
|
||||
|
||||
const { popover } = playColumnTouchVoiceSelection(canvasElement);
|
||||
expect(args.onTranslateClick).toHaveBeenCalledTimes(1);
|
||||
expect(popover?.hidden).not.toBe(false);
|
||||
},
|
||||
|
|
|
|||
|
|
@ -157,9 +157,7 @@ export type LanguagePairSelectProps = {
|
|||
onResponseLanguageSelect?: (lang: ResponseLang) => void | Promise<void>;
|
||||
controlsRef?: (controls: LanguagePairSelectControls) => void;
|
||||
};
|
||||
export function LanguagePairSelect(
|
||||
props: LanguagePairSelectProps,
|
||||
): JSX.Element {
|
||||
function LanguagePairSelect(props: LanguagePairSelectProps): JSX.Element {
|
||||
const fromLangsOptions = genSelectOptionsByLangs(availableLangs);
|
||||
const toLangsOptions = genSelectOptionsByLangs(availableTTS);
|
||||
|
||||
|
|
|
|||
|
|
@ -44,11 +44,6 @@ export const DEFAULT_AUTO_VOLUME = 15;
|
|||
*/
|
||||
export const DEFAULT_SMART_DUCKING_STRENGTH = 80;
|
||||
|
||||
/**
|
||||
* Max audio volume percentage (if available)
|
||||
*/
|
||||
export const maxAudioVolume = 900;
|
||||
|
||||
/**
|
||||
* The number of repeated responses after which the message turns into
|
||||
* "translation is delayed, please wait"
|
||||
|
|
|
|||
|
|
@ -5,7 +5,7 @@ import {
|
|||
type AuthMessageData,
|
||||
} from "../../types/core/auth/message";
|
||||
|
||||
export function createAuthDataMessage(data: AuthMessageData): AuthDataMessage {
|
||||
function createAuthDataMessage(data: AuthMessageData): AuthDataMessage {
|
||||
return {
|
||||
source: AUTH_DATA_MESSAGE_SOURCE,
|
||||
type: AUTH_DATA_MESSAGE_TYPE,
|
||||
|
|
|
|||
|
|
@ -22,13 +22,11 @@ import { GM_fetch } from "../../utils/gm";
|
|||
import { votStorage } from "../../utils/storage";
|
||||
import { base64UrlEncode } from "../../utils/utils";
|
||||
|
||||
export function createCodeVerifier(): string {
|
||||
function createCodeVerifier(): string {
|
||||
return base64UrlEncode(crypto.getRandomValues(new Uint8Array(32)));
|
||||
}
|
||||
|
||||
export async function createCodeChallenge(
|
||||
codeVerifier: string,
|
||||
): Promise<string> {
|
||||
async function createCodeChallenge(codeVerifier: string): Promise<string> {
|
||||
const digest = await crypto.subtle.digest(
|
||||
"SHA-256",
|
||||
new TextEncoder().encode(codeVerifier),
|
||||
|
|
@ -37,7 +35,7 @@ export async function createCodeChallenge(
|
|||
return base64UrlEncode(new Uint8Array(digest));
|
||||
}
|
||||
|
||||
export const isTokenError = (data: unknown): data is TokenError => {
|
||||
const isTokenError = (data: unknown): data is TokenError => {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
|
|
@ -46,7 +44,7 @@ export const isTokenError = (data: unknown): data is TokenError => {
|
|||
);
|
||||
};
|
||||
|
||||
export async function getOAuthTokenByCode(code: string): Promise<TokenInfo> {
|
||||
async function getOAuthTokenByCode(code: string): Promise<TokenInfo> {
|
||||
const res = await GM_fetch(YANDEX_AUTH_TOKEN_URL, {
|
||||
method: "POST",
|
||||
headers: {
|
||||
|
|
@ -70,7 +68,7 @@ export async function getOAuthTokenByCode(code: string): Promise<TokenInfo> {
|
|||
return data;
|
||||
}
|
||||
|
||||
export const isAuthError = (data: unknown): data is AuthError => {
|
||||
const isAuthError = (data: unknown): data is AuthError => {
|
||||
return (
|
||||
typeof data === "object" &&
|
||||
data !== null &&
|
||||
|
|
@ -79,7 +77,7 @@ export const isAuthError = (data: unknown): data is AuthError => {
|
|||
);
|
||||
};
|
||||
|
||||
export async function getUserInfo(token: string): Promise<UserInfo> {
|
||||
async function getUserInfo(token: string): Promise<UserInfo> {
|
||||
const res = await GM_fetch(`${YANDEX_USER_INFO_URL}?format=json`, {
|
||||
method: "GET",
|
||||
headers: {
|
||||
|
|
|
|||
|
|
@ -7,9 +7,7 @@ export type BootstrapPolicyInput = {
|
|||
authOrigin: string;
|
||||
};
|
||||
|
||||
export function shouldSkipIframeBootstrap(
|
||||
input: BootstrapPolicyInput,
|
||||
): boolean {
|
||||
function shouldSkipIframeBootstrap(input: BootstrapPolicyInput): boolean {
|
||||
if (!input.isIframe) return false;
|
||||
return (
|
||||
input.href === "about:blank" ||
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ import type {
|
|||
import { computeExpiresAt } from "../utils/responseCache";
|
||||
import { votStorage } from "../utils/storage";
|
||||
|
||||
export const YANDEX_TTL_MS = 2 * 60 * 60 * 1000; // 2 hours
|
||||
const YANDEX_TTL_MS = 2 * 60 * 60 * 1000; // 2 hours
|
||||
const VOT_SESSION_STORAGE_KEY = "VOTSession";
|
||||
|
||||
type TimedCacheEntry<T> = {
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { containsCrossShadow } from "../utils/dom";
|
||||
|
||||
export interface DocumentWithFullscreen extends Document {
|
||||
interface DocumentWithFullscreen extends Document {
|
||||
webkitFullscreenElement?: Element | null;
|
||||
webkitExitFullscreen?: () => Promise<void>;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,13 +1,8 @@
|
|||
export const EXTERNAL_VOLUME_HOSTS = new Set(["youtube", "googledrive"]);
|
||||
const EXTERNAL_VOLUME_HOSTS = new Set(["youtube", "googledrive"]);
|
||||
const YOUTUBE_LIKE_HOSTS = EXTERNAL_VOLUME_HOSTS;
|
||||
|
||||
export const YOUTUBE_LIKE_HOSTS = EXTERNAL_VOLUME_HOSTS;
|
||||
|
||||
export const MUTE_SYNC_DISABLED_HOSTS = new Set(["rutube", "ok"]);
|
||||
export const TRANSLATION_DOWNLOAD_HOSTS = new Set([
|
||||
"youtube",
|
||||
"invidious",
|
||||
"piped",
|
||||
]);
|
||||
const MUTE_SYNC_DISABLED_HOSTS = new Set(["rutube", "ok"]);
|
||||
const TRANSLATION_DOWNLOAD_HOSTS = new Set(["youtube", "invidious", "piped"]);
|
||||
|
||||
export function isExternalVolumeHost(host: string): boolean {
|
||||
return EXTERNAL_VOLUME_HOSTS.has(host);
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export type LifecycleTranslationResetHost = {
|
|||
resetSubtitlesWidget(): void;
|
||||
};
|
||||
|
||||
export function resetLifecycleTranslation(
|
||||
function resetLifecycleTranslation(
|
||||
host: LifecycleTranslationResetHost,
|
||||
options: {
|
||||
requireVideoData?: boolean;
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export type OverlayMountTargets = {
|
|||
subtitlesMountContainer: HTMLElement | ShadowRoot;
|
||||
};
|
||||
|
||||
export function resolveOverlayBaseContainer(
|
||||
function resolveOverlayBaseContainer(
|
||||
container: HTMLElement,
|
||||
site: ServiceConf,
|
||||
): HTMLElement {
|
||||
|
|
|
|||
|
|
@ -9,17 +9,19 @@ import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
|||
import { AudioDownloader } from "../audioDownloader";
|
||||
import { STREAM_TIMEOUT_MS } from "../audioDownloader/strategies/webAudioBridge";
|
||||
import { t } from "../localization/localizationProvider";
|
||||
import { deleteAccount } from "../stores/account";
|
||||
import type {
|
||||
DownloadedAudioData,
|
||||
DownloadedPartialAudioData,
|
||||
} from "../types/audioDownloader";
|
||||
import type { VideoData } from "../types/videoHandler";
|
||||
import {
|
||||
createAbortableDelay,
|
||||
createAbortableWaiter,
|
||||
NEVER_ABORTED_SIGNAL,
|
||||
throwIfAborted,
|
||||
} from "../utils/abort";
|
||||
import { deleteAccount, hasAccountToken } from "../utils/account";
|
||||
import { hasAccountToken } from "../utils/account";
|
||||
import debug from "../utils/debug";
|
||||
import {
|
||||
getErrorMessage,
|
||||
|
|
@ -29,7 +31,6 @@ import {
|
|||
} from "../utils/errors";
|
||||
import type { VideoHandler } from "../VideoHandler";
|
||||
import VOTLocalizedError from "../VOTLocalizedError";
|
||||
import type { VideoData } from "../videoHandler/shared";
|
||||
import { openAuthWindow } from "./auth/window";
|
||||
import {
|
||||
getTranslationAuthErrorKind,
|
||||
|
|
|
|||
|
|
@ -1,11 +1,12 @@
|
|||
import type { VideoDataSubtitle } from "@vot.js/core/types/client";
|
||||
import type { ServiceConf, VideoService } from "@vot.js/ext/types/service";
|
||||
import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
||||
|
||||
import type { OverlayViewControls } from "../components/OverlayView/OverlayView";
|
||||
import type { StorageData } from "../types/storage";
|
||||
import type { VideoData } from "../types/videoHandler";
|
||||
import debug from "../utils/debug";
|
||||
import { containsCrossShadow } from "../utils/dom";
|
||||
import type { VideoData } from "../videoHandler/shared";
|
||||
import { findConnectedContainerBySelector } from "./containerResolution";
|
||||
import { hideLifecycleOverlay, resetAndHideLifecycle } from "./lifecycleShared";
|
||||
|
||||
|
|
|
|||
|
|
@ -3,11 +3,13 @@ import { getVideoData } from "@vot.js/ext/utils/videoData";
|
|||
import votConfig from "@vot.js/shared/config";
|
||||
import { availableLangs } from "@vot.js/shared/consts";
|
||||
import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
||||
|
||||
import {
|
||||
getYoutubeAudioFormatLanguage as getYoutubeAudioFormatLanguageTag,
|
||||
selectSmallestAudioFormat,
|
||||
} from "../audioDownloader/utils";
|
||||
import { localizationProvider } from "../localization/localizationProvider";
|
||||
import type { VideoData as RuntimeVideoData } from "../types/videoHandler";
|
||||
import debug from "../utils/debug";
|
||||
import { GM_fetch } from "../utils/gm";
|
||||
import { cleanText } from "../utils/text";
|
||||
|
|
@ -19,7 +21,6 @@ import {
|
|||
} from "../utils/volume";
|
||||
import type { VideoHandler } from "../VideoHandler";
|
||||
import VOTLocalizedError from "../VOTLocalizedError";
|
||||
import type { VideoData as RuntimeVideoData } from "../videoHandler/shared";
|
||||
import { isExternalVolumeHost } from "./hostPolicies";
|
||||
import { detect } from "./translateApis";
|
||||
|
||||
|
|
@ -272,7 +273,7 @@ function resolveYoutubeDetectedLanguageFromSubtitles(
|
|||
);
|
||||
}
|
||||
|
||||
export async function resolveDetectedLanguageForVideo(
|
||||
async function resolveDetectedLanguageForVideo(
|
||||
options: ResolveDetectedLanguageOptions,
|
||||
): Promise<ResolveDetectedLanguageResult> {
|
||||
if (options.isStream) {
|
||||
|
|
|
|||
|
|
@ -190,14 +190,11 @@ async function ensureDnrHeaderStripRule(
|
|||
return isNew;
|
||||
}
|
||||
|
||||
export async function ensureDnrOriginStripRuleForYoutubei(
|
||||
url: string,
|
||||
forbiddenHeaders: Record<string, string> = {},
|
||||
): Promise<boolean> {
|
||||
if (!isYoutubeMobileUrl(url)) return false;
|
||||
|
||||
const requestHeaders: DnrRequestHeader[] = [...YOUTUBEI_BASE_HEADERS];
|
||||
|
||||
function buildRequestHeaders(
|
||||
baseHeaders: DnrRequestHeader[],
|
||||
forbiddenHeaders: Record<string, string>,
|
||||
): DnrRequestHeader[] {
|
||||
const requestHeaders: DnrRequestHeader[] = [...baseHeaders];
|
||||
for (const [header, value] of Object.entries(forbiddenHeaders)) {
|
||||
const name = normalizeHeaderName(header);
|
||||
if (!name || name.toLowerCase() === "origin") continue;
|
||||
|
|
@ -207,6 +204,18 @@ export async function ensureDnrOriginStripRuleForYoutubei(
|
|||
value: String(value),
|
||||
});
|
||||
}
|
||||
return requestHeaders;
|
||||
}
|
||||
|
||||
export async function ensureDnrOriginStripRuleForYoutubei(
|
||||
url: string,
|
||||
forbiddenHeaders: Record<string, string> = {},
|
||||
): Promise<boolean> {
|
||||
if (!isYoutubeMobileUrl(url)) return false;
|
||||
const requestHeaders = buildRequestHeaders(
|
||||
YOUTUBEI_BASE_HEADERS,
|
||||
forbiddenHeaders,
|
||||
);
|
||||
|
||||
return ensureDnrHeaderStripRule(
|
||||
url,
|
||||
|
|
@ -222,18 +231,10 @@ export async function ensureDnrStripRuleForGooglevideo(
|
|||
forbiddenHeaders: Record<string, string> = {},
|
||||
): Promise<boolean> {
|
||||
if (!isGooglevideoUrl(url)) return false;
|
||||
|
||||
const requestHeaders: DnrRequestHeader[] = [...GOOGLEVIDEO_BASE_HEADERS];
|
||||
|
||||
for (const [header, value] of Object.entries(forbiddenHeaders)) {
|
||||
const name = normalizeHeaderName(header);
|
||||
if (!name || name.toLowerCase() === "origin") continue;
|
||||
requestHeaders.push({
|
||||
header: name,
|
||||
operation: "set",
|
||||
value: String(value),
|
||||
});
|
||||
}
|
||||
const requestHeaders = buildRequestHeaders(
|
||||
GOOGLEVIDEO_BASE_HEADERS,
|
||||
forbiddenHeaders,
|
||||
);
|
||||
|
||||
return ensureDnrHeaderStripRule(
|
||||
url,
|
||||
|
|
|
|||
|
|
@ -1,6 +1,5 @@
|
|||
import debug from "../../utils/debug";
|
||||
import {
|
||||
type AnyObject,
|
||||
BG_MSG_NOTIFICATION,
|
||||
type BridgeWireMessage,
|
||||
isOurMessage,
|
||||
|
|
@ -10,11 +9,7 @@ import {
|
|||
TYPE_XHR_ABORT,
|
||||
TYPE_XHR_START,
|
||||
} from "../shared/constants";
|
||||
import {
|
||||
getSameWindowPostMessageTargetOrigin,
|
||||
isSameWindowBridgeEvent,
|
||||
toPageMessage,
|
||||
} from "../shared/transport";
|
||||
import { isSameWindowBridgeEvent, postToPage } from "../shared/transport";
|
||||
import { asErrorMessage } from "../shared/utils";
|
||||
import { ext, runtimeSendMessage } from "../shared/webext";
|
||||
import { handleBridgeRequest } from "./request-handler";
|
||||
|
|
@ -44,16 +39,6 @@ function injectPageModule(fileName: string): void {
|
|||
parent.appendChild(script);
|
||||
}
|
||||
|
||||
function postToPage(payload: AnyObject) {
|
||||
const { message, transfer } = toPageMessage(payload);
|
||||
const targetOrigin = getSameWindowPostMessageTargetOrigin();
|
||||
if (transfer.length) {
|
||||
globalThis.postMessage(message, targetOrigin, transfer);
|
||||
return;
|
||||
}
|
||||
globalThis.postMessage(message, targetOrigin);
|
||||
}
|
||||
|
||||
function sendResponse(
|
||||
id: string,
|
||||
ok: boolean,
|
||||
|
|
|
|||
|
|
@ -8,10 +8,7 @@ import {
|
|||
} from "../shared/bodySerialization";
|
||||
import type { AnyObject } from "../shared/constants";
|
||||
import { PORT_NAME, TYPE_XHR_ACK, TYPE_XHR_EVENT } from "../shared/constants";
|
||||
import {
|
||||
getSameWindowPostMessageTargetOrigin,
|
||||
toPageMessage,
|
||||
} from "../shared/transport";
|
||||
import { postToPage } from "../shared/transport";
|
||||
import { ext, runtimeMessagesUseStructuredClone } from "../shared/webext";
|
||||
import {
|
||||
isYandexApiHostname,
|
||||
|
|
@ -178,16 +175,6 @@ function mergeHeadersIfMissing(
|
|||
}
|
||||
}
|
||||
|
||||
function postToPage(payload: AnyObject) {
|
||||
const { message, transfer } = toPageMessage(payload);
|
||||
const targetOrigin = getSameWindowPostMessageTargetOrigin();
|
||||
if (transfer.length) {
|
||||
globalThis.postMessage(message, targetOrigin, transfer);
|
||||
return;
|
||||
}
|
||||
globalThis.postMessage(message, targetOrigin);
|
||||
}
|
||||
|
||||
function settleXhrPort(requestId: string, state: XhrPortState): void {
|
||||
state.settled = true;
|
||||
try {
|
||||
|
|
|
|||
|
|
@ -60,3 +60,13 @@ export function toPageMessage(payload: AnyObject): {
|
|||
transfer: getTransferables(payload),
|
||||
};
|
||||
}
|
||||
|
||||
export function postToPage(payload: AnyObject): void {
|
||||
const { message, transfer } = toPageMessage(payload);
|
||||
const targetOrigin = getSameWindowPostMessageTargetOrigin();
|
||||
if (transfer.length) {
|
||||
globalThis.postMessage(message, targetOrigin, transfer);
|
||||
return;
|
||||
}
|
||||
globalThis.postMessage(message, targetOrigin);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -36,7 +36,7 @@ const availableLocales: readonly LangOverride[] = (() => {
|
|||
: (["auto", ...locales] as LangOverride[]);
|
||||
})();
|
||||
|
||||
export function resolveRuntimeLocaleVersion(
|
||||
function resolveRuntimeLocaleVersion(
|
||||
buildVersion: string,
|
||||
scriptVersion: string,
|
||||
) {
|
||||
|
|
|
|||
|
|
@ -1,9 +1,11 @@
|
|||
import type VOTClient from "@vot.js/ext";
|
||||
import { createStore } from "solid-js/store";
|
||||
import type { Account } from "../types/storage";
|
||||
import { hasValidAccountToken } from "../utils/account";
|
||||
|
||||
import type { Account, StorageData } from "../types/storage";
|
||||
import { hasValidAccountToken, isAccountExpired } from "../utils/account";
|
||||
import { votStorage } from "../utils/storage";
|
||||
|
||||
export type AccountStore = {
|
||||
type AccountStore = {
|
||||
isRefreshing: boolean;
|
||||
isLoggedIn: boolean;
|
||||
username?: string;
|
||||
|
|
@ -12,6 +14,11 @@ export type AccountStore = {
|
|||
token?: string;
|
||||
};
|
||||
|
||||
type AccountStateOwner = {
|
||||
data?: Partial<StorageData>;
|
||||
votClient?: VOTClient;
|
||||
};
|
||||
|
||||
function createInitialState(): AccountStore {
|
||||
return {
|
||||
isRefreshing: false,
|
||||
|
|
@ -46,3 +53,30 @@ export async function updateAccountFromStorage() {
|
|||
const data = await votStorage.get<Partial<Account>>("account", {});
|
||||
updateAccount(data);
|
||||
}
|
||||
|
||||
function clearAccountState(owner?: AccountStateOwner): void {
|
||||
if (owner?.data) {
|
||||
owner.data.account = {};
|
||||
}
|
||||
if (owner?.votClient) {
|
||||
owner.votClient.provider.apiToken = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAccount(owner?: AccountStateOwner): Promise<void> {
|
||||
clearAccountState(owner);
|
||||
await votStorage.delete("account");
|
||||
resetAccount();
|
||||
}
|
||||
|
||||
export async function deleteExpiredAccount(
|
||||
owner?: AccountStateOwner,
|
||||
now = Date.now(),
|
||||
): Promise<boolean> {
|
||||
if (!isAccountExpired(owner?.data?.account, now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await deleteAccount(owner);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,6 +1,6 @@
|
|||
import { createStore } from "solid-js/store";
|
||||
|
||||
export type LocaleStore = {
|
||||
type LocaleStore = {
|
||||
updatedAt: number;
|
||||
hash: string;
|
||||
};
|
||||
|
|
@ -15,7 +15,3 @@ function createInitialState(): LocaleStore {
|
|||
export const [locale, setLocale] = createStore<LocaleStore>(
|
||||
createInitialState(),
|
||||
);
|
||||
|
||||
export function resetLocale() {
|
||||
setLocale(createInitialState());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -20,7 +20,7 @@ import type { DetectService, TranslateService } from "../types/translateApis";
|
|||
import { isSupportGMXhr } from "../utils/gm";
|
||||
import { calculatedResLang } from "../utils/localization";
|
||||
|
||||
export type SettingsStore = {
|
||||
type SettingsStore = {
|
||||
// menu
|
||||
defaultVolume: number;
|
||||
responseLanguage: ResponseLang;
|
||||
|
|
@ -116,7 +116,3 @@ function createInitialState(): SettingsStore {
|
|||
export const [settings, setSettings] = createStore<SettingsStore>(
|
||||
createInitialState(),
|
||||
);
|
||||
|
||||
export function resetSettings() {
|
||||
setSettings(createInitialState());
|
||||
}
|
||||
|
|
|
|||
|
|
@ -499,6 +499,15 @@ const buildStyledSpans = (segments: StyledTextSegment[]) => {
|
|||
return { text, styledSpans };
|
||||
};
|
||||
|
||||
const prepareDisplayText = (
|
||||
text: string,
|
||||
): { text: string; leadingTrim: number } => {
|
||||
const normalized = text.replaceAll("\u00A0", " ");
|
||||
const leadingTrim = normalized.length - normalized.trimStart().length;
|
||||
const trimmedEnd = Math.max(leadingTrim, normalized.trimEnd().length);
|
||||
return { text: normalized.slice(leadingTrim, trimmedEnd), leadingTrim };
|
||||
};
|
||||
|
||||
const trimStyledDisplayResult = (
|
||||
text: string,
|
||||
styledSpans: SubtitleStyledSpan[],
|
||||
|
|
@ -506,14 +515,7 @@ const trimStyledDisplayResult = (
|
|||
text: string;
|
||||
styledSpans: SubtitleStyledSpan[];
|
||||
} => {
|
||||
const normalizedText = text.replaceAll("\u00A0", " ");
|
||||
const leadingTrim = normalizedText.length - normalizedText.trimStart().length;
|
||||
const trailingTrim = normalizedText.length - normalizedText.trimEnd().length;
|
||||
const trimmedEnd = Math.max(
|
||||
leadingTrim,
|
||||
normalizedText.length - trailingTrim,
|
||||
);
|
||||
const finalText = normalizedText.slice(leadingTrim, trimmedEnd);
|
||||
const { text: finalText, leadingTrim } = prepareDisplayText(text);
|
||||
const finalSpans = styledSpans
|
||||
.map((span) => ({
|
||||
start: Math.max(0, span.start - leadingTrim),
|
||||
|
|
@ -533,14 +535,8 @@ const trimStyledDisplayResult = (
|
|||
};
|
||||
|
||||
const trimPlainDisplayText = (text: string): string => {
|
||||
const normalizedText = text.replaceAll("\u00A0", " ");
|
||||
const leadingTrim = normalizedText.length - normalizedText.trimStart().length;
|
||||
const trailingTrim = normalizedText.length - normalizedText.trimEnd().length;
|
||||
const trimmedEnd = Math.max(
|
||||
leadingTrim,
|
||||
normalizedText.length - trailingTrim,
|
||||
);
|
||||
return normalizedText.slice(leadingTrim, trimmedEnd);
|
||||
const { text: trimmed } = prepareDisplayText(text);
|
||||
return trimmed;
|
||||
};
|
||||
|
||||
const buildPlainDisplayModel = (
|
||||
|
|
|
|||
|
|
@ -13,10 +13,6 @@ export class FullscreenLayerController {
|
|||
this.container = container;
|
||||
}
|
||||
|
||||
getWidgetParentElement(): HTMLElement | ShadowRoot {
|
||||
return this.container;
|
||||
}
|
||||
|
||||
getLayoutRootElement(): HTMLElement {
|
||||
return this.container instanceof ShadowRoot
|
||||
? (this.container.host as HTMLElement)
|
||||
|
|
|
|||
|
|
@ -27,7 +27,7 @@ export type HighlightState = {
|
|||
applied: Uint8Array;
|
||||
};
|
||||
|
||||
export const NO_HIGHLIGHT_INDEX = -1;
|
||||
const NO_HIGHLIGHT_INDEX = -1;
|
||||
const PASSED_CLASS = "passed";
|
||||
|
||||
export function createHighlightState(): HighlightState {
|
||||
|
|
|
|||
|
|
@ -11,7 +11,7 @@
|
|||
// - Eye-tracking research (Szarkowska et al.) showing that breaking inside a
|
||||
// phrase increases regressions and dwell time.
|
||||
|
||||
export type LineBreakLexicon = {
|
||||
type LineBreakLexicon = {
|
||||
// Words that must not end line 1 (they bind forward to the next word).
|
||||
bindsForward: Set<string>;
|
||||
// Words that read better at the start of line 2.
|
||||
|
|
@ -392,7 +392,7 @@ const SCRIPTIO_CONTINUA = new Set([
|
|||
"bo",
|
||||
]);
|
||||
|
||||
export const getBaseLanguage = (locale?: string): string =>
|
||||
const getBaseLanguage = (locale?: string): string =>
|
||||
(locale ?? "").toLowerCase().split(/[-_]/u)[0] ?? "";
|
||||
|
||||
export const isScriptioContinua = (locale?: string): boolean =>
|
||||
|
|
@ -400,20 +400,20 @@ export const isScriptioContinua = (locale?: string): boolean =>
|
|||
|
||||
const WORD_CHARS_RE = /[^\p{L}\p{N}'\u2019]+/gu;
|
||||
|
||||
export const normalizeLexiconWord = (value: string): string =>
|
||||
const normalizeLexiconWord = (value: string): string =>
|
||||
value.replaceAll(WORD_CHARS_RE, " ").trim().toLowerCase();
|
||||
|
||||
export const lastWordOf = (text: string): string => {
|
||||
const lastWordOf = (text: string): string => {
|
||||
const parts = normalizeLexiconWord(text).split(" ").filter(Boolean);
|
||||
return parts.at(-1) ?? "";
|
||||
};
|
||||
|
||||
export const firstWordOf = (text: string): string => {
|
||||
const firstWordOf = (text: string): string => {
|
||||
const parts = normalizeLexiconWord(text).split(" ").filter(Boolean);
|
||||
return parts[0] ?? "";
|
||||
};
|
||||
|
||||
export const LINE_BREAK_PENALTY = {
|
||||
const LINE_BREAK_PENALTY = {
|
||||
bindsForward: 240,
|
||||
prefersLineStart: -60,
|
||||
} as const;
|
||||
|
|
|
|||
|
|
@ -35,7 +35,7 @@ export type CapturedVerticalAnchorState = {
|
|||
travelPx: number;
|
||||
};
|
||||
|
||||
export function getVerticalAnchorBounds({
|
||||
function getVerticalAnchorBounds({
|
||||
elementHeight,
|
||||
boxHeight,
|
||||
bottomInset,
|
||||
|
|
|
|||
|
|
@ -26,15 +26,6 @@ import {
|
|||
import { shouldInsertSpaceBetweenTextFragments } from "./textSpacing";
|
||||
import { parseSubtitleDescriptor } from "./types";
|
||||
|
||||
export type {
|
||||
ProcessedSubtitles,
|
||||
SubtitleDescriptor,
|
||||
SubtitleLine,
|
||||
SubtitlesClient,
|
||||
SubtitleToken,
|
||||
VideoDataForSubtitles,
|
||||
} from "../types/subtitles";
|
||||
|
||||
type ProcessedSubtitlesInput = Partial<ProcessedSubtitles>;
|
||||
|
||||
type YoutubeSubtitleSegment = {
|
||||
|
|
|
|||
|
|
@ -1060,9 +1060,7 @@ export const sortProcessedSubtitles = (
|
|||
),
|
||||
});
|
||||
|
||||
export const toSubtitlesData = (
|
||||
processed: ProcessedSubtitles,
|
||||
): SubtitlesData => {
|
||||
const toSubtitlesData = (processed: ProcessedSubtitles): SubtitlesData => {
|
||||
const subtitles = processed.subtitles.map((line) => ({
|
||||
text: line.text,
|
||||
startMs: line.startMs,
|
||||
|
|
|
|||
|
|
@ -1,27 +1,16 @@
|
|||
export type {
|
||||
BuiltInSubtitleFontFamily,
|
||||
SubtitleDescriptor,
|
||||
SubtitleFontFamily,
|
||||
SubtitleFormat,
|
||||
SubtitleToken,
|
||||
VideoDataForSubtitles,
|
||||
} from "../types/subtitles";
|
||||
export {
|
||||
import type { SubtitleDescriptor } from "../types/subtitles";
|
||||
import {
|
||||
type BuiltInSubtitleFontFamily,
|
||||
type SubtitleFormat,
|
||||
subtitleFontFamilies,
|
||||
subtitleFormats,
|
||||
} from "../types/subtitles";
|
||||
|
||||
import type { SubtitleDescriptor } from "../types/subtitles";
|
||||
import { subtitleFontFamilies, subtitleFormats } from "../types/subtitles";
|
||||
|
||||
const subtitleFormatsSet = new Set(subtitleFormats);
|
||||
|
||||
export function isSubtitleFormat(
|
||||
value: unknown,
|
||||
): value is import("../types/subtitles").SubtitleFormat {
|
||||
function isSubtitleFormat(value: unknown): value is SubtitleFormat {
|
||||
return (
|
||||
typeof value === "string" &&
|
||||
subtitleFormatsSet.has(value as import("../types/subtitles").SubtitleFormat)
|
||||
typeof value === "string" && subtitleFormatsSet.has(value as SubtitleFormat)
|
||||
);
|
||||
}
|
||||
|
||||
|
|
@ -64,8 +53,6 @@ export function parseSubtitleDescriptor(
|
|||
|
||||
export function isBuiltInSubtitleFontFamily(
|
||||
fontFamily: string,
|
||||
): fontFamily is import("../types/subtitles").BuiltInSubtitleFontFamily {
|
||||
return subtitleFontFamilies.includes(
|
||||
fontFamily as import("../types/subtitles").BuiltInSubtitleFontFamily,
|
||||
);
|
||||
): fontFamily is BuiltInSubtitleFontFamily {
|
||||
return subtitleFontFamilies.includes(fontFamily as BuiltInSubtitleFontFamily);
|
||||
}
|
||||
|
|
|
|||
|
|
@ -66,7 +66,7 @@ export function findNextCueBoundaryMs(
|
|||
}
|
||||
|
||||
/** Earliest word-highlight threshold strictly after `timeMs`. */
|
||||
export function findNextThresholdMs(
|
||||
function findNextThresholdMs(
|
||||
timeMs: number,
|
||||
thresholds: readonly number[],
|
||||
): number | null {
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ export const positions = [
|
|||
] as const;
|
||||
export type Position = (typeof positions)[number];
|
||||
|
||||
export const directions = ["default", "row", "column"] as const;
|
||||
const directions = ["default", "row", "column"] as const;
|
||||
export type Direction = (typeof directions)[number];
|
||||
|
||||
export type Status = "none" | "error" | "success";
|
||||
|
|
|
|||
|
|
@ -41,7 +41,7 @@ export const subtitleFontFamilyCss = {
|
|||
barlow: `"Barlow", ${subtitleBaseFontFamilyCSS}`,
|
||||
} as const satisfies Record<BuiltInSubtitleFontFamily, string>;
|
||||
|
||||
export const subtitlePositionPresets = [
|
||||
const subtitlePositionPresets = [
|
||||
"bottom-center",
|
||||
"center",
|
||||
"top-center",
|
||||
|
|
|
|||
|
|
@ -1,13 +1,7 @@
|
|||
import type { IntervalIdleChecker } from "../utils/intervalIdleChecker";
|
||||
import type { VideoHandler } from "../VideoHandler";
|
||||
import type { Direction, Position } from "./components/votButton";
|
||||
import type { StorageData } from "./storage";
|
||||
|
||||
export type ButtonLayout = {
|
||||
direction: Direction;
|
||||
position: Position;
|
||||
};
|
||||
|
||||
export type OverlayMount = {
|
||||
root: HTMLElement | ShadowRoot;
|
||||
portalContainer: HTMLElement;
|
||||
|
|
|
|||
|
|
@ -4,6 +4,7 @@ import { serializeProcessedSubtitles } from "../subtitles/standards";
|
|||
import type { Status } from "../types/components/votButton";
|
||||
import type { StorageData } from "../types/storage";
|
||||
import type { OverlayMount, UIManagerProps } from "../types/uiManager";
|
||||
import type { VideoData } from "../types/videoHandler";
|
||||
import debug from "../utils/debug";
|
||||
import { downloadTranslation } from "../utils/download";
|
||||
import { GM_fetch } from "../utils/gm";
|
||||
|
|
@ -15,7 +16,6 @@ import {
|
|||
downloadBlob,
|
||||
} from "../utils/utils";
|
||||
import type { VideoHandler } from "../VideoHandler";
|
||||
import type { VideoData } from "../videoHandler/shared";
|
||||
import { safeSetPlayerVolume } from "../videoHandler/translationVolume";
|
||||
import { applyOverlayMountUpdate } from "./mount";
|
||||
import { OverlayController } from "./overlayController";
|
||||
|
|
|
|||
|
|
@ -54,7 +54,6 @@ import {
|
|||
import { account, resetAccount, updateAccount } from "../stores/account";
|
||||
import { setLocale } from "../stores/locale";
|
||||
import { setSettings } from "../stores/settings";
|
||||
import type { SubtitleFormat } from "../subtitles/types";
|
||||
import type { Position } from "../types/components/votButton";
|
||||
import type {
|
||||
Account,
|
||||
|
|
@ -62,7 +61,7 @@ import type {
|
|||
StorageData,
|
||||
TranslateProxyStatus,
|
||||
} from "../types/storage";
|
||||
import type { SubtitleFontFamily } from "../types/subtitles";
|
||||
import type { SubtitleFontFamily, SubtitleFormat } from "../types/subtitles";
|
||||
import type { TranslateService } from "../types/translateApis";
|
||||
import debug from "../utils/debug";
|
||||
import { EventImpl } from "../utils/eventImpl";
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import { openAuthWindow } from "../core/auth/window";
|
||||
import { t } from "../localization/localizationProvider";
|
||||
import { deleteExpiredAccount } from "../stores/account";
|
||||
import type { Status } from "../types/components/votButton";
|
||||
import { deleteExpiredAccount } from "../utils/account";
|
||||
import debug from "../utils/debug";
|
||||
import { isAbortError } from "../utils/errors";
|
||||
import type { VideoHandler } from "../VideoHandler";
|
||||
|
|
|
|||
|
|
@ -1,11 +1,4 @@
|
|||
import type VOTClient from "@vot.js/ext";
|
||||
import type { StorageData } from "../types/storage";
|
||||
import { votStorage } from "./storage";
|
||||
|
||||
type AccountStateOwner = {
|
||||
data?: Partial<StorageData>;
|
||||
votClient?: VOTClient;
|
||||
};
|
||||
|
||||
export function hasAccountToken(account?: Partial<StorageData["account"]>) {
|
||||
return typeof account?.token === "string" && account.token.length > 0;
|
||||
|
|
@ -29,29 +22,3 @@ export function hasValidAccountToken(
|
|||
) {
|
||||
return hasAccountToken(account) && !isAccountExpired(account, now);
|
||||
}
|
||||
|
||||
export function clearAccountState(owner?: AccountStateOwner): void {
|
||||
if (owner?.data) {
|
||||
owner.data.account = {};
|
||||
}
|
||||
if (owner?.votClient) {
|
||||
owner.votClient.provider.apiToken = undefined;
|
||||
}
|
||||
}
|
||||
|
||||
export async function deleteAccount(owner?: AccountStateOwner): Promise<void> {
|
||||
clearAccountState(owner);
|
||||
await votStorage.delete("account");
|
||||
}
|
||||
|
||||
export async function deleteExpiredAccount(
|
||||
owner?: AccountStateOwner,
|
||||
now = Date.now(),
|
||||
): Promise<boolean> {
|
||||
if (!isAccountExpired(owner?.data?.account, now)) {
|
||||
return false;
|
||||
}
|
||||
|
||||
await deleteAccount(owner);
|
||||
return true;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -37,42 +37,6 @@ export function containsCrossShadow(container: Node, target: Node): boolean {
|
|||
return false;
|
||||
}
|
||||
|
||||
export function isDocumentViewportElement(
|
||||
element: Element | null | undefined,
|
||||
): boolean {
|
||||
return element === document.body || element === document.documentElement;
|
||||
}
|
||||
|
||||
export function resolveScopedFullscreenElement(
|
||||
fullscreenEl: Element | null | undefined,
|
||||
anchors: Array<Node | null | undefined>,
|
||||
options: {
|
||||
allowDocumentViewport?: boolean;
|
||||
} = {},
|
||||
): HTMLElement | null {
|
||||
const { allowDocumentViewport = false } = options;
|
||||
|
||||
if (!(fullscreenEl instanceof HTMLElement)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (isDocumentViewportElement(fullscreenEl) && !allowDocumentViewport) {
|
||||
return null;
|
||||
}
|
||||
|
||||
for (const anchor of anchors) {
|
||||
if (
|
||||
anchor &&
|
||||
(containsCrossShadow(fullscreenEl, anchor) ||
|
||||
containsCrossShadow(anchor, fullscreenEl))
|
||||
) {
|
||||
return fullscreenEl;
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
type CrossShadowTarget = Element | Document;
|
||||
|
||||
export function closestCrossShadow(
|
||||
|
|
|
|||
|
|
@ -3,7 +3,7 @@ export type ComposedTreeNode<T> = {
|
|||
getRootNode?: () => Node | null | undefined;
|
||||
};
|
||||
|
||||
export function getComposedParentElement<T extends object>(
|
||||
function getComposedParentElement<T extends object>(
|
||||
node: ComposedTreeNode<T> | null | undefined,
|
||||
): T | null {
|
||||
if (!node) return null;
|
||||
|
|
|
|||
|
|
@ -1,16 +1,15 @@
|
|||
import { isDocumentHidden } from "./environment";
|
||||
|
||||
export type IntervalIdleMode = "active" | "idle" | "hidden";
|
||||
type IntervalIdleMode = "active" | "idle" | "hidden";
|
||||
type IntervalIdleTickSource = "start" | "interval" | "immediate";
|
||||
|
||||
export type IntervalIdleTickSource = "start" | "interval" | "immediate";
|
||||
|
||||
export type IntervalIdleTickContext = {
|
||||
type IntervalIdleTickContext = {
|
||||
nowMs: number;
|
||||
mode: IntervalIdleMode;
|
||||
source: IntervalIdleTickSource;
|
||||
};
|
||||
|
||||
export type IntervalIdleProfile = {
|
||||
type IntervalIdleProfile = {
|
||||
/**
|
||||
* Polling interval used by the checker loop.
|
||||
* Mirrors the fixed `IDLE_CHECK_INTERVAL_MS` approach from `app.js`.
|
||||
|
|
@ -35,7 +34,7 @@ export type IntervalIdleProfile = {
|
|||
|
||||
type IntervalIdleSubscriber = (ctx: IntervalIdleTickContext) => void;
|
||||
|
||||
export type IntervalIdleSubscribeOptions = {
|
||||
type IntervalIdleSubscribeOptions = {
|
||||
/**
|
||||
* Reports whether this subscriber currently has work that a periodic tick
|
||||
* could perform. When every subscriber reports `false`, the checker stops its
|
||||
|
|
|
|||
|
|
@ -259,16 +259,25 @@ class VOTStorage {
|
|||
return globalThis.localStorage.setItem(name, JSON.stringify(value));
|
||||
}
|
||||
|
||||
private async getChangeContext(name: string): Promise<{
|
||||
support: StorageSupport;
|
||||
storageKey: StorageKey;
|
||||
shouldNotify: boolean;
|
||||
oldValue: unknown;
|
||||
}> {
|
||||
const support = this.resolveSupport();
|
||||
const shouldNotify = this.shouldUseSyntheticListeners(support);
|
||||
const oldValue = shouldNotify ? await this.getRaw(name) : undefined;
|
||||
|
||||
return { support, storageKey: name as StorageKey, shouldNotify, oldValue };
|
||||
}
|
||||
|
||||
async setRaw<T extends KeysOrDefaultValue = undefined>(
|
||||
name: string,
|
||||
value: T,
|
||||
): Promise<void> {
|
||||
const support = this.resolveSupport();
|
||||
const storageKey = name as StorageKey;
|
||||
const shouldNotify = this.shouldUseSyntheticListeners(support);
|
||||
const oldValue = shouldNotify
|
||||
? await this.getRaw<T | undefined>(name)
|
||||
: undefined;
|
||||
const { support, storageKey, shouldNotify, oldValue } =
|
||||
await this.getChangeContext(name);
|
||||
|
||||
if (support.promiseSet && GM.setValue) {
|
||||
await GM.setValue(name, value);
|
||||
|
|
@ -298,10 +307,8 @@ class VOTStorage {
|
|||
}
|
||||
|
||||
async deleteRaw(name: string): Promise<void> {
|
||||
const support = this.resolveSupport();
|
||||
const storageKey = name as StorageKey;
|
||||
const shouldNotify = this.shouldUseSyntheticListeners(support);
|
||||
const oldValue = shouldNotify ? await this.getRaw(name) : undefined;
|
||||
const { support, storageKey, shouldNotify, oldValue } =
|
||||
await this.getChangeContext(name);
|
||||
|
||||
if (support.promiseDelete && GM.deleteValue) {
|
||||
await GM.deleteValue(name);
|
||||
|
|
@ -343,15 +350,14 @@ class VOTStorage {
|
|||
| ((id: unknown) => unknown)
|
||||
| undefined)
|
||||
: undefined;
|
||||
|
||||
if (typeof addListener === "function") {
|
||||
const gmListener = this.createTypedListener(listener);
|
||||
const listenerId = addListener(name, gmListener);
|
||||
return () => {
|
||||
if (typeof removeListener === "function") {
|
||||
removeListener(listenerId);
|
||||
}
|
||||
};
|
||||
const unsubscribe = this.registerGMListener(
|
||||
addListener,
|
||||
removeListener,
|
||||
name,
|
||||
listener,
|
||||
);
|
||||
if (unsubscribe) {
|
||||
return unsubscribe;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -371,15 +377,14 @@ class VOTStorage {
|
|||
}
|
||||
).GM_removeValueChangeListener
|
||||
: undefined;
|
||||
|
||||
if (typeof addListener === "function") {
|
||||
const gmListener = this.createTypedListener(listener);
|
||||
const listenerId = addListener(name, gmListener);
|
||||
return () => {
|
||||
if (typeof removeListener === "function") {
|
||||
removeListener(listenerId);
|
||||
}
|
||||
};
|
||||
const unsubscribe = this.registerGMListener(
|
||||
addListener,
|
||||
removeListener,
|
||||
name,
|
||||
listener,
|
||||
);
|
||||
if (unsubscribe) {
|
||||
return unsubscribe;
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -423,6 +428,29 @@ class VOTStorage {
|
|||
};
|
||||
}
|
||||
|
||||
private registerGMListener(
|
||||
addListener:
|
||||
| ((
|
||||
key: string,
|
||||
callback: StorageValueChangeListener<unknown>,
|
||||
) => unknown)
|
||||
| undefined,
|
||||
removeListener: ((id: unknown) => unknown) | undefined,
|
||||
name: StorageKey,
|
||||
listener: StorageValueChangeListener<unknown>,
|
||||
): (() => void) | undefined {
|
||||
if (typeof addListener !== "function") {
|
||||
return undefined;
|
||||
}
|
||||
|
||||
const listenerId = addListener(name, this.createTypedListener(listener));
|
||||
return () => {
|
||||
if (typeof removeListener === "function") {
|
||||
removeListener(listenerId);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
private getLocalStorageListeners(name: StorageKey) {
|
||||
const existing = this.localStorageListeners.get(name);
|
||||
if (existing) {
|
||||
|
|
|
|||
|
|
@ -103,11 +103,6 @@ export function fnv1a32ToKeyPart(str: string): string {
|
|||
return (hash >>> 0).toString(36);
|
||||
}
|
||||
|
||||
export interface DocumentWithFullscreen extends Document {
|
||||
webkitFullscreenElement?: Element | null;
|
||||
webkitExitFullscreen?: () => Promise<void>;
|
||||
}
|
||||
|
||||
export const isPiPAvailable = () => Boolean(document.pictureInPictureEnabled);
|
||||
|
||||
async function writeBlobToHandle(
|
||||
|
|
|
|||
|
|
@ -427,7 +427,7 @@ function bindGlobalDismissAndHotkeys(ctx: ExtraEventsContext): void {
|
|||
self.container.draggable = false;
|
||||
}
|
||||
}
|
||||
export function bindPlaybackRefreshOnResume(ctx: ExtraEventsContext): void {
|
||||
function bindPlaybackRefreshOnResume(ctx: ExtraEventsContext): void {
|
||||
const { self, add } = ctx;
|
||||
let wasPausedSinceLastPlay = false;
|
||||
|
||||
|
|
|
|||
|
|
@ -21,7 +21,7 @@ export function isProxyClientEnabled(config: ProxyConfig): boolean {
|
|||
return Boolean(config.translateProxyEnabled);
|
||||
}
|
||||
|
||||
export function isProxyRoutingEnabled(config: ProxyConfig): boolean {
|
||||
function isProxyRoutingEnabled(config: ProxyConfig): boolean {
|
||||
return config.translateProxyEnabled === 2;
|
||||
}
|
||||
|
||||
|
|
|
|||
|
|
@ -7,7 +7,7 @@ import {
|
|||
import type {
|
||||
SubtitleDescriptor,
|
||||
VideoDataForSubtitles,
|
||||
} from "../../subtitles/types";
|
||||
} from "../../types/subtitles";
|
||||
import debug from "../../utils/debug";
|
||||
import type { VideoHandler } from "../../VideoHandler";
|
||||
import { proxifyYandexSubtitlesUrl } from "./proxyShared";
|
||||
|
|
|
|||
|
|
@ -2,15 +2,13 @@
|
|||
localizationProvider,
|
||||
t,
|
||||
} from "../../localization/localizationProvider";
|
||||
import {
|
||||
parseSubtitleDescriptor,
|
||||
type SubtitleDescriptor,
|
||||
} from "../../subtitles/types";
|
||||
import { parseSubtitleDescriptor } from "../../subtitles/types";
|
||||
import {
|
||||
AUTO_SUBTITLE_LANGUAGE_VALUE,
|
||||
ORIGINAL_SUBTITLE_LANGUAGE_VALUE,
|
||||
type ResponseLanguageSubtitles,
|
||||
} from "../../types/storage";
|
||||
import type { SubtitleDescriptor } from "../../types/subtitles";
|
||||
|
||||
export type IndexedSubtitleDescriptor = {
|
||||
descriptor: SubtitleDescriptor;
|
||||
|
|
|
|||
|
|
@ -3,11 +3,11 @@ import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
|||
import { isTranslationDownloadHost } from "../../core/hostPolicies";
|
||||
import { notifyTranslationFailureIfNeeded } from "../../core/translationErrors";
|
||||
import { t } from "../../localization/localizationProvider";
|
||||
import type { VideoData } from "../../types/videoHandler";
|
||||
import debug from "../../utils/debug";
|
||||
import { toErrorMessage } from "../../utils/errors";
|
||||
import type { VideoHandler } from "../../VideoHandler";
|
||||
import VOTLocalizedError from "../../VOTLocalizedError";
|
||||
import type { VideoData } from "../shared";
|
||||
import { applyTranslationPlaybackVolume } from "../translationVolume";
|
||||
import {
|
||||
isYandexAudioUrlOrProxy,
|
||||
|
|
|
|||
|
|
@ -1,7 +1,7 @@
|
|||
import type { RequestLang, ResponseLang } from "@vot.js/shared/types/data";
|
||||
|
||||
import type { CacheTranslationSuccess } from "../../types/core/cacheManager";
|
||||
import type { VideoData } from "../shared";
|
||||
import type { VideoData } from "../../types/videoHandler";
|
||||
import type { ActionContext } from "./translationTypes";
|
||||
|
||||
export type TranslationAudioResult = {
|
||||
|
|
@ -68,7 +68,7 @@ export async function requestTranslationAudio(
|
|||
};
|
||||
}
|
||||
|
||||
export function buildTranslationCacheValue(options: {
|
||||
function buildTranslationCacheValue(options: {
|
||||
videoId: string;
|
||||
requestLang: string;
|
||||
responseLang: string;
|
||||
|
|
|
|||
|
|
@ -1,8 +1,3 @@
|
|||
export type {
|
||||
VideoData,
|
||||
VideoData as RuntimeVideoData,
|
||||
} from "../types/videoHandler";
|
||||
|
||||
/**
|
||||
* Country code used for proxy settings. Populated lazily during init.
|
||||
*/
|
||||
|
|
|
|||
|
|
@ -6,7 +6,7 @@ export type VolumeLinkState = {
|
|||
lastTranslationPercent: number;
|
||||
};
|
||||
|
||||
export type VolumeLinkDirection = "translation" | "video";
|
||||
type VolumeLinkDirection = "translation" | "video";
|
||||
|
||||
type ApplyVolumeLinkDeltaInput = {
|
||||
state: VolumeLinkState;
|
||||
|
|
|
|||
|
|
@ -8,7 +8,7 @@ import {
|
|||
getReleaseDownloadBase,
|
||||
} from "../paths";
|
||||
|
||||
export async function writeChromeUpdatesManifest({
|
||||
async function writeChromeUpdatesManifest({
|
||||
version,
|
||||
extensionId,
|
||||
}: {
|
||||
|
|
|
|||
|
|
@ -45,7 +45,7 @@ function normalizeHostPermission(
|
|||
return `*://*.${value}/*`;
|
||||
}
|
||||
|
||||
export function normalizeHostPermissions(list: string[] = []): string[] {
|
||||
function normalizeHostPermissions(list: string[] = []): string[] {
|
||||
const normalized = list
|
||||
.map((item) => normalizeHostPermission(item))
|
||||
.filter((value): value is string => Boolean(value));
|
||||
|
|
@ -87,7 +87,7 @@ function normalizeWebAccessibleResourceMatches(
|
|||
];
|
||||
}
|
||||
|
||||
export function createWebAccessibleResources(
|
||||
function createWebAccessibleResources(
|
||||
matches: string[],
|
||||
): Array<Record<string, unknown>> {
|
||||
const normalizedMatches = normalizeWebAccessibleResourceMatches(matches);
|
||||
|
|
@ -107,7 +107,7 @@ export function createWebAccessibleResources(
|
|||
// Match splitting (origin-fallback vs direct)
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
export function splitMatchesForOriginFallback(matches: string[] = []): {
|
||||
function splitMatchesForOriginFallback(matches: string[] = []): {
|
||||
originFallbackMatches: string[];
|
||||
directMatches: string[];
|
||||
} {
|
||||
|
|
@ -129,19 +129,6 @@ export function splitMatchesForOriginFallback(matches: string[] = []): {
|
|||
return { originFallbackMatches, directMatches };
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Icons
|
||||
// ----------------------------------------------------------------
|
||||
|
||||
export function buildIconsMap(
|
||||
sizes: readonly number[] = EXTENSION_ICON_SIZES,
|
||||
pathPrefix: string = "icons",
|
||||
): Record<number, string> {
|
||||
return Object.fromEntries(
|
||||
sizes.map((size) => [size, `${pathPrefix}/icon-${size}.png`]),
|
||||
) as Record<number, string>;
|
||||
}
|
||||
|
||||
// ----------------------------------------------------------------
|
||||
// Content scripts
|
||||
// ----------------------------------------------------------------
|
||||
|
|
@ -152,7 +139,7 @@ export interface ManifestPathStrategy {
|
|||
background?: Record<string, unknown>;
|
||||
}
|
||||
|
||||
export function createContentScriptEntries({
|
||||
function createContentScriptEntries({
|
||||
matches,
|
||||
excludeMatches,
|
||||
includeWorld,
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ export const OUT_TEMP_DIR = path.resolve(DIST_EXT_DIR, "_tmp");
|
|||
|
||||
export const EXTENSION_NAME = "vot-extension";
|
||||
export const FIREFOX_EXTENSION_NAME = `${EXTENSION_NAME}-firefox`;
|
||||
export const CHROME_EXTENSION_NAME = `${EXTENSION_NAME}-chrome`;
|
||||
const CHROME_EXTENSION_NAME = `${EXTENSION_NAME}-chrome`;
|
||||
|
||||
export const FIREFOX_UPDATES_MANIFEST_FILE = `${FIREFOX_EXTENSION_NAME}-updates.json`;
|
||||
export const FIREFOX_UPDATES_MANIFEST_PATH = path.join(
|
||||
|
|
@ -35,7 +35,7 @@ export function viteCacheDir(name: string): string {
|
|||
return path.resolve(ROOT_DIR, "node_modules", ".vite", name);
|
||||
}
|
||||
|
||||
export const nodeCryptoAlias = {
|
||||
const nodeCryptoAlias = {
|
||||
"node:crypto": path.resolve(SOURCE_DIR, "shims", "nodeCrypto.ts"),
|
||||
} as const;
|
||||
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue