From 39902cb1c6851ddb1d00f7053f7a2ffbcf8ec17b Mon Sep 17 00:00:00 2001 From: Kevin Gibbons Date: Tue, 25 Oct 2022 19:43:58 -0700 Subject: [PATCH] Format/slightly modernize the JS (#345) * add basic JS tooling * fix accidental uses of global variables * auto-format * add and fix a couple more standard lint rules * remove useless return false from settimeout callbacks * document JS contributing * fix whitespace in package.json * add JS stuff to codespell skiplist * codespell take two * update github action and add comments about duplicated logic --- .eslintrc.js | 17 + .github/workflows/lint_python.yml | 4 +- .gitignore | 5 +- CONTRIBUTING.md | 4 + deploy.sh | 4 +- package-lock.json | 1761 +++++++++++++++++++++++++++ package.json | 17 + tubearchivist/static/cast-videos.js | 243 ++-- tubearchivist/static/progress.js | 160 +-- tubearchivist/static/script.js | 1693 ++++++++++++------------- 10 files changed, 2879 insertions(+), 1029 deletions(-) create mode 100644 .eslintrc.js create mode 100644 package-lock.json create mode 100644 package.json diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 0000000..1c044e1 --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,17 @@ +'use strict'; +module.exports = { + extends: ['eslint:recommended', 'eslint-config-prettier'], + parserOptions: { + ecmaVersion: 2020, + }, + env: { + browser: true, + }, + rules: { + strict: ['error', 'global'], + 'no-unused-vars': ['error', { vars: 'local' }], + eqeqeq: ['error', 'always', { null: 'ignore' }], + curly: ['error', 'multi-line'], + 'no-var': 'error', + }, +}; diff --git a/.github/workflows/lint_python.yml b/.github/workflows/lint_python.yml index 09bd1cf..3c23d76 100644 --- a/.github/workflows/lint_python.yml +++ b/.github/workflows/lint_python.yml @@ -6,11 +6,13 @@ jobs: steps: - uses: actions/checkout@v2 - uses: actions/setup-python@v2 + # note: this logic is duplicated in the `validate` function in ./deploy.sh + # if you update this file, you should update that as well - run: pip install --upgrade pip wheel - run: pip install bandit black codespell flake8 flake8-bugbear flake8-comprehensions isort - run: black --check --diff --line-length 79 . - - run: codespell + - run: codespell --skip="./.git,./package.json,./package-lock.json,./node_modules" - run: flake8 . --count --max-complexity=10 --max-line-length=79 --show-source --statistics - run: isort --check-only --line-length 79 --profile black . diff --git a/.gitignore b/.gitignore index 5445a53..14fd5ce 100644 --- a/.gitignore +++ b/.gitignore @@ -5,4 +5,7 @@ __pycache__ db.sqlite3 # vscode custom conf -.vscode \ No newline at end of file +.vscode + +# JavaScript stuff +node_modules diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 43708b9..aa02441 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -76,6 +76,10 @@ Do you see anything on the roadmap that you would like to take a closer look at To fix a bug or implement a feature, fork the repository and make all changes to the testing branch. When ready, create a pull request. +## Making changes to the JavaScript + +The JavaScript does not require any build step; you just edit the files directly. However, there is config for eslint and prettier (a linter and formatter respectively); their use is recommended but not required. To use them, install `node`, run `npm i` from the root directory of this repository to install dependencies, then run `npm run lint` and `npm run format` to run eslint and prettier respectively. + ## Releases There are three different docker tags: diff --git a/deploy.sh b/deploy.sh index 631e97f..e9513f8 100755 --- a/deploy.sh +++ b/deploy.sh @@ -82,10 +82,12 @@ function validate { echo "run validate on $check_path" + # note: this logic is duplicated in the `./github/workflows/lint_python.yml` config + # if you update this file, you should update that as well echo "running black" black --diff --color --check -l 79 "$check_path" echo "running codespell" - codespell --skip="./.git" "$check_path" + codespell --skip="./.git,./package.json,./package-lock.json,./node_modules" "$check_path" echo "running flake8" flake8 "$check_path" --count --max-complexity=10 --max-line-length=79 \ --show-source --statistics diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..22e8152 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1761 @@ +{ + "name": "tubearchivist", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "dependencies": { + "eslint-config-prettier": "^8.5.0" + }, + "devDependencies": { + "eslint": "^8.26.0", + "prettier": "^2.7.1" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.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/@humanwhocodes/config-array": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", + "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==", + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "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/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "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/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", + "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "dependencies": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "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": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "bin": { + "eslint-config-prettier": "bin/cli.js" + }, + "peerDependencies": { + "eslint": ">=7.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "dependencies": { + "eslint-visitor-keys": "^2.0.0" + }, + "engines": { + "node": "^10.0.0 || ^12.0.0 || >= 14.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=5" + } + }, + "node_modules/eslint-utils/node_modules/eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==", + "engines": { + "node": ">=10" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==", + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "dependencies": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "node_modules/fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "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": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "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==" + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==", + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "node_modules/js-sdsl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" + }, + "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/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "dependencies": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "engines": { + "node": ">=8" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + } + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "engines": { + "iojs": ">=1.0.0", + "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==", + "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", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "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/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==", + "engines": { + "node": ">=0.10.0" + } + }, + "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==" + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@eslint/eslintrc": { + "version": "1.3.3", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz", + "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==", + "requires": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.4.0", + "globals": "^13.15.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" + } + }, + "@humanwhocodes/config-array": { + "version": "0.11.6", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.6.tgz", + "integrity": "sha512-jJr+hPTJYKyDILJfhNSHsjiwXYf26Flsz8DvNndOsHs5pwSnpGUEy8yzF0JYhCEvTDdV2vuOK5tt8BVhwO5/hg==", + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.4" + } + }, + "@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==" + }, + "@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==" + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==" + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "acorn": { + "version": "8.8.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz", + "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==" + }, + "acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==" + }, + "balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==" + }, + "brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==" + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "requires": { + "ms": "2.1.2" + } + }, + "deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==" + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "requires": { + "esutils": "^2.0.2" + } + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==" + }, + "eslint": { + "version": "8.26.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.26.0.tgz", + "integrity": "sha512-kzJkpaw1Bfwheq4VXUezFriD1GxszX6dUekM7Z3aC2o4hju+tsR/XyTC3RcoSD7jmy9VkPU3+N6YjVU2e96Oyg==", + "requires": { + "@eslint/eslintrc": "^1.3.3", + "@humanwhocodes/config-array": "^0.11.6", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "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": "^7.1.1", + "eslint-utils": "^3.0.0", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.4.0", + "esquery": "^1.4.0", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.15.0", + "grapheme-splitter": "^1.0.4", + "ignore": "^5.2.0", + "import-fresh": "^3.0.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-sdsl": "^4.1.4", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.1", + "regexpp": "^3.2.0", + "strip-ansi": "^6.0.1", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-config-prettier": { + "version": "8.5.0", + "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz", + "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==", + "requires": {} + }, + "eslint-scope": { + "version": "7.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz", + "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==", + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz", + "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==", + "requires": { + "eslint-visitor-keys": "^2.0.0" + }, + "dependencies": { + "eslint-visitor-keys": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz", + "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==" + } + } + }, + "eslint-visitor-keys": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz", + "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==" + }, + "espree": { + "version": "9.4.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.0.tgz", + "integrity": "sha512-DQmnRpLj7f6TgN/NYb0MTzJXL+vJF9h3pHy4JhCIs3zwcgez8xmGg3sXHcEO97BrmO2OSvCwMdfdlyl+E9KjOw==", + "requires": { + "acorn": "^8.8.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.3.0" + } + }, + "esquery": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz", + "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==", + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==" + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==" + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==" + }, + "fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==" + }, + "fastq": { + "version": "1.13.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.13.0.tgz", + "integrity": "sha512-YpkpUnK8od0o1hmeSc7UUs/eB/vIPWJYjKck2QKIzAf71Vm1AAQ3EbuZB3g2JIy+pg+ERD0vqI79KyZiB2e2Nw==", + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "requires": { + "flat-cache": "^3.0.4" + } + }, + "find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==" + }, + "fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==" + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.17.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.17.0.tgz", + "integrity": "sha512-1C+6nQRb1GwGMKm2dH/E7enFAMxGTmGI7/dEdhy/DNelv85w9B72t3uc5frtMNXIbzrarJJ/lTCjcaZwbLJmyw==", + "requires": { + "type-fest": "^0.20.2" + } + }, + "grapheme-splitter": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz", + "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ==" + }, + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==" + }, + "ignore": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.0.tgz", + "integrity": "sha512-CmxgYGiEPCLhfLnpPp1MoRmifwEIOgjcHXxOBjv7mY96c+eWScsOP9c112ZyLdWHi0FxHjI+4uVhKYp/gcdRmQ==" + }, + "import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==" + }, + "inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==" + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==" + }, + "is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==" + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==" + }, + "js-sdsl": { + "version": "4.1.5", + "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.1.5.tgz", + "integrity": "sha512-08bOAKweV2NUC1wqTtf3qZlnpOX/R2DU9ikpjOHs0H+ibQv3zpncVQg6um4uYtRtrwIX8M4Nh3ytK4HGlYAq7Q==" + }, + "js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "requires": { + "argparse": "^2.0.1" + } + }, + "json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==" + }, + "json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==" + }, + "levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==" + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==" + }, + "once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.1", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz", + "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==", + "requires": { + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0", + "word-wrap": "^1.2.3" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "requires": { + "p-limit": "^3.0.2" + } + }, + "parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "requires": { + "callsites": "^3.0.0" + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==" + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==" + }, + "prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==" + }, + "prettier": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.7.1.tgz", + "integrity": "sha512-ujppO+MkdPqoVINuDFDRLClm7D78qbDt0/NR+wp5FqEZOoTNAjPHWj17QRhu7geIHJfcNhRk1XVQmF8Bp3ye+g==", + "dev": true + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==" + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==" + }, + "regexpp": { + "version": "3.2.0", + "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz", + "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==" + }, + "resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==" + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==" + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "requires": { + "glob": "^7.1.3" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==" + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==" + }, + "supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "requires": { + "has-flag": "^4.0.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==" + }, + "type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "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==" + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "requires": { + "punycode": "^2.1.0" + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "requires": { + "isexe": "^2.0.0" + } + }, + "word-wrap": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz", + "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==" + }, + "wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==" + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==" + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 0000000..0bd1e84 --- /dev/null +++ b/package.json @@ -0,0 +1,17 @@ +{ + "private": true, + "scripts": { + "lint": "eslint 'tubearchivist/static/**/*.js'", + "format": "prettier --write 'tubearchivist/static/**/*.js'" + }, + "devDependencies": { + "eslint": "^8.26.0", + "prettier": "^2.7.1", + "eslint-config-prettier": "^8.5.0" + }, + "prettier": { + "singleQuote": true, + "arrowParens": "avoid", + "printWidth": 100 + } +} diff --git a/tubearchivist/static/cast-videos.js b/tubearchivist/static/cast-videos.js index 4334d04..881b55f 100644 --- a/tubearchivist/static/cast-videos.js +++ b/tubearchivist/static/cast-videos.js @@ -1,148 +1,157 @@ +'use strict'; + +/* global cast chrome getVideoPlayerVideoId postVideoProgress setProgressBar getVideoPlayer getVideoPlayerWatchStatus watchedThreshold isWatched getVideoData getURL getVideoPlayerCurrentTime */ + function initializeCastApi() { - cast.framework.CastContext.getInstance().setOptions({ - receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in receiver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee. - autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED - }); + cast.framework.CastContext.getInstance().setOptions({ + receiverApplicationId: chrome.cast.media.DEFAULT_MEDIA_RECEIVER_APP_ID, // Use built in receiver app on cast device, see https://developers.google.com/cast/docs/styled_receiver if you want to be able to add a theme, splash screen or watermark. Has a $5 one time fee. + autoJoinPolicy: chrome.cast.AutoJoinPolicy.ORIGIN_SCOPED, + }); - var player = new cast.framework.RemotePlayer(); - var playerController = new cast.framework.RemotePlayerController(player); + let player = new cast.framework.RemotePlayer(); + let playerController = new cast.framework.RemotePlayerController(player); - // Add event listerner to check if a connection to a cast device is initiated - playerController.addEventListener( - cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, function() { - castConnectionChange(player) - } - ); - playerController.addEventListener( - cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, function() { - castVideoProgress(player) - } - ); - playerController.addEventListener( - cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, function() { - castVideoPaused(player) - } - ); + // Add event listerner to check if a connection to a cast device is initiated + playerController.addEventListener( + cast.framework.RemotePlayerEventType.IS_CONNECTED_CHANGED, + function () { + castConnectionChange(player); + } + ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.CURRENT_TIME_CHANGED, + function () { + castVideoProgress(player); + } + ); + playerController.addEventListener( + cast.framework.RemotePlayerEventType.IS_PAUSED_CHANGED, + function () { + castVideoPaused(player); + } + ); } - function castConnectionChange(player) { - // If cast connection is initialized start cast - if (player.isConnected) { - // console.log("Cast Connected."); - castStart(); - } else if (!player.isConnected) { - // console.log("Cast Disconnected."); - } + // If cast connection is initialized start cast + if (player.isConnected) { + // console.log("Cast Connected."); + castStart(); + } else if (!player.isConnected) { + // console.log("Cast Disconnected."); + } } function castVideoProgress(player) { - var videoId = getVideoPlayerVideoId(); - if (player.mediaInfo.contentId.includes(videoId)) { - var currentTime = player.currentTime; - var duration = player.duration; - if ((currentTime % 10) <= 1.0 && currentTime != 0 && duration != 0) { // Check progress every 10 seconds or else progress is checked a few times a second - postVideoProgress(videoId, currentTime); - setProgressBar(videoId, currentTime, duration); - if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched - if (watchedThreshold(currentTime, duration)) { - isWatched(videoId); - } - } + let videoId = getVideoPlayerVideoId(); + if (player.mediaInfo.contentId.includes(videoId)) { + let currentTime = player.currentTime; + let duration = player.duration; + if (currentTime % 10 <= 1.0 && currentTime !== 0 && duration !== 0) { + // Check progress every 10 seconds or else progress is checked a few times a second + postVideoProgress(videoId, currentTime); + setProgressBar(videoId, currentTime, duration); + if (!getVideoPlayerWatchStatus()) { + // Check if video is already marked as watched + if (watchedThreshold(currentTime, duration)) { + isWatched(videoId); } + } } + } } function castVideoPaused(player) { - var videoId = getVideoPlayerVideoId(); - var currentTime = player.currentTime; - var duration = player.duration; - if (player.mediaInfo != null) { - if (player.mediaInfo.contentId.includes(videoId)) { - if (currentTime != 0 && duration != 0) { - postVideoProgress(videoId, currentTime); - } - } + let videoId = getVideoPlayerVideoId(); + let currentTime = player.currentTime; + let duration = player.duration; + if (player.mediaInfo != null) { + if (player.mediaInfo.contentId.includes(videoId)) { + if (currentTime !== 0 && duration !== 0) { + postVideoProgress(videoId, currentTime); + } } + } } function castStart() { - var castSession = cast.framework.CastContext.getInstance().getCurrentSession(); - // Check if there is already media playing on the cast target to prevent recasting on page reload or switching to another video page - if (!castSession.getMediaSession()) { - var videoId = getVideoPlayerVideoId(); - var videoData = getVideoData(videoId); - var contentId = getURL() + videoData.data.media_url; - var contentTitle = videoData.data.title; - var contentImage = getURL() + videoData.data.vid_thumb_url; + let castSession = cast.framework.CastContext.getInstance().getCurrentSession(); + // Check if there is already media playing on the cast target to prevent recasting on page reload or switching to another video page + if (!castSession.getMediaSession()) { + let videoId = getVideoPlayerVideoId(); + let videoData = getVideoData(videoId); + let contentId = getURL() + videoData.data.media_url; + let contentTitle = videoData.data.title; + let contentImage = getURL() + videoData.data.vid_thumb_url; - contentType = 'video/mp4'; // Set content type, only videos right now so it is hard coded - contentCurrentTime = getVideoPlayerCurrentTime(); // Get video's current position - contentActiveSubtitle = []; - // Check if a subtitle is turned on. - for (var i = 0; i < getVideoPlayer().textTracks.length; i++) { - if (getVideoPlayer().textTracks[i].mode == "showing") { - contentActiveSubtitle =[i + 1]; - } - } - contentSubtitles = []; - var videoSubtitles = videoData.data.subtitles; // Array of subtitles - if (typeof(videoSubtitles) != 'undefined' && videoData.config.downloads.subtitle) { - for (var i = 0; i < videoSubtitles.length; i++) { - subtitle = new chrome.cast.media.Track(i, chrome.cast.media.TrackType.TEXT); - subtitle.trackContentId = videoSubtitles[i].media_url; - subtitle.trackContentType = 'text/vtt'; - subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES; - subtitle.name = videoSubtitles[i].name; - subtitle.language = videoSubtitles[i].lang; - subtitle.customData = null; - contentSubtitles.push(subtitle); - } - } + let contentType = 'video/mp4'; // Set content type, only videos right now so it is hard coded + let contentCurrentTime = getVideoPlayerCurrentTime(); // Get video's current position + let contentActiveSubtitle = []; + // Check if a subtitle is turned on. + for (let i = 0; i < getVideoPlayer().textTracks.length; i++) { + if (getVideoPlayer().textTracks[i].mode === 'showing') { + contentActiveSubtitle = [i + 1]; + } + } + let contentSubtitles = []; + let videoSubtitles = videoData.data.subtitles; // Array of subtitles + if (typeof videoSubtitles !== 'undefined' && videoData.config.downloads.subtitle) { + for (let i = 0; i < videoSubtitles.length; i++) { + let subtitle = new chrome.cast.media.Track(i, chrome.cast.media.TrackType.TEXT); + subtitle.trackContentId = videoSubtitles[i].media_url; + subtitle.trackContentType = 'text/vtt'; + subtitle.subtype = chrome.cast.media.TextTrackType.SUBTITLES; + subtitle.name = videoSubtitles[i].name; + subtitle.language = videoSubtitles[i].lang; + subtitle.customData = null; + contentSubtitles.push(subtitle); + } + } - mediaInfo = new chrome.cast.media.MediaInfo(contentId, contentType); // Create MediaInfo var that contains url and content type - // mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; // Set type of stream, BUFFERED, LIVE, OTHER - mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); // Create metadata var and add it to MediaInfo - mediaInfo.metadata.title = contentTitle.replace("&", "&"); // Set the video title - mediaInfo.metadata.images = [new chrome.cast.Image(contentImage)]; // Set the video thumbnail - // mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle(); - mediaInfo.tracks = contentSubtitles; + let mediaInfo = new chrome.cast.media.MediaInfo(contentId, contentType); // Create MediaInfo var that contains url and content type + // mediaInfo.streamType = chrome.cast.media.StreamType.BUFFERED; // Set type of stream, BUFFERED, LIVE, OTHER + mediaInfo.metadata = new chrome.cast.media.GenericMediaMetadata(); // Create metadata var and add it to MediaInfo + mediaInfo.metadata.title = contentTitle.replace('&', '&'); // Set the video title + mediaInfo.metadata.images = [new chrome.cast.Image(contentImage)]; // Set the video thumbnail + // mediaInfo.textTrackStyle = new chrome.cast.media.TextTrackStyle(); + mediaInfo.tracks = contentSubtitles; - var request = new chrome.cast.media.LoadRequest(mediaInfo); // Create request with the previously set MediaInfo. - // request.queueData = new chrome.cast.media.QueueData(); // See https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.QueueData for playlist support. - request.currentTime = shiftCurrentTime(contentCurrentTime); // Set video start position based on the browser video position - request.activeTrackIds = contentActiveSubtitle; // Set active subtitle based on video player - // request.autoplay = false; // Set content to auto play, true by default - castSession.loadMedia(request).then( - function() { - castSuccessful(); - }, - function() { - castFailed(errorCode); - } - ); // Send request to cast device - } + let request = new chrome.cast.media.LoadRequest(mediaInfo); // Create request with the previously set MediaInfo. + // request.queueData = new chrome.cast.media.QueueData(); // See https://developers.google.com/cast/docs/reference/web_sender/chrome.cast.media.QueueData for playlist support. + request.currentTime = shiftCurrentTime(contentCurrentTime); // Set video start position based on the browser video position + request.activeTrackIds = contentActiveSubtitle; // Set active subtitle based on video player + // request.autoplay = false; // Set content to auto play, true by default + castSession.loadMedia(request).then( + function () { + castSuccessful(); + }, + function (error) { + castFailed(error.code); + } + ); // Send request to cast device + } } -function shiftCurrentTime(contentCurrentTime) { // Shift media back 3 seconds to prevent missing some of the content - if (contentCurrentTime > 5) { - return(contentCurrentTime - 3); - } else { - return(0); - } +function shiftCurrentTime(contentCurrentTime) { + // Shift media back 3 seconds to prevent missing some of the content + if (contentCurrentTime > 5) { + return contentCurrentTime - 3; + } else { + return 0; + } } function castSuccessful() { - // console.log('Cast Successful.'); - getVideoPlayer().pause(); // Pause browser video on successful cast + // console.log('Cast Successful.'); + getVideoPlayer().pause(); // Pause browser video on successful cast } function castFailed(errorCode) { - console.log('Error code: ' + errorCode); + console.log('Error code: ' + errorCode); } -window['__onGCastApiAvailable'] = function(isAvailable) { - if (isAvailable) { - initializeCastApi(); - } -} +window['__onGCastApiAvailable'] = function (isAvailable) { + if (isAvailable) { + initializeCastApi(); + } +}; diff --git a/tubearchivist/static/progress.js b/tubearchivist/static/progress.js index 34a586e..171c4eb 100644 --- a/tubearchivist/static/progress.js +++ b/tubearchivist/static/progress.js @@ -1,106 +1,110 @@ /** * Handle multi channel notifications - * + * */ -checkMessages() +'use strict'; + +checkMessages(); // page map to notification status const messageTypes = { - "download": ["message:download", "message:add", "message:rescan", "message:playlistscan"], - "channel": ["message:subchannel"], - "channel_id": ["message:playlistscan"], - "playlist": ["message:subplaylist"], - "setting": ["message:setting"] -} + download: ['message:download', 'message:add', 'message:rescan', 'message:playlistscan'], + channel: ['message:subchannel'], + channel_id: ['message:playlistscan'], + playlist: ['message:subplaylist'], + setting: ['message:setting'], +}; // start to look for messages function checkMessages() { - var notifications = document.getElementById("notifications"); - if (notifications) { - var dataOrigin = notifications.getAttribute("data"); - getMessages(dataOrigin); - } + let notifications = document.getElementById('notifications'); + if (notifications) { + let dataOrigin = notifications.getAttribute('data'); + getMessages(dataOrigin); + } } // get messages for page on timer function getMessages(dataOrigin) { - fetch('/progress/').then(response => { - return response.json(); - }).then(responseData => { - var messages = buildMessage(responseData, dataOrigin); - if (messages.length > 0) { - // restart itself - setTimeout(function() { - getMessages(dataOrigin); - }, 3000); - }; + fetch('/progress/') + .then(response => { + return response.json(); + }) + .then(responseData => { + let messages = buildMessage(responseData, dataOrigin); + if (messages.length > 0) { + // restart itself + setTimeout(function () { + getMessages(dataOrigin); + }, 3000); + } }); } // make div for all messages, return relevant function buildMessage(responseData, dataOrigin) { - // filter relevan messages - var allMessages = responseData["messages"]; - var messages = allMessages.filter(function(value) { - return messageTypes[dataOrigin].includes(value["status"]) - }, dataOrigin); - // build divs - var notificationDiv = document.getElementById("notifications"); - var nots = notificationDiv.childElementCount; - notificationDiv.innerHTML = ""; - for (let i = 0; i < messages.length; i++) { - var messageData = messages[i]; - var messageStatus = messageData["status"]; - var messageBox = document.createElement("div"); - var title = document.createElement("h3"); - title.innerHTML = messageData["title"]; - var message = document.createElement("p"); - message.innerHTML = messageData["message"]; - messageBox.appendChild(title); - messageBox.appendChild(message); - messageBox.classList.add(messageData["level"], "notification"); - notificationDiv.appendChild(messageBox); - if (messageStatus === "message:download") { - checkDownloadIcons(); - }; - }; - // reload page when no more notifications - if (nots > 0 && messages.length === 0) { - location.reload(); - }; - return messages + // filter relevan messages + let allMessages = responseData['messages']; + let messages = allMessages.filter(function (value) { + return messageTypes[dataOrigin].includes(value['status']); + }, dataOrigin); + // build divs + let notificationDiv = document.getElementById('notifications'); + let nots = notificationDiv.childElementCount; + notificationDiv.innerHTML = ''; + for (let i = 0; i < messages.length; i++) { + let messageData = messages[i]; + let messageStatus = messageData['status']; + let messageBox = document.createElement('div'); + let title = document.createElement('h3'); + title.innerHTML = messageData['title']; + let message = document.createElement('p'); + message.innerHTML = messageData['message']; + messageBox.appendChild(title); + messageBox.appendChild(message); + messageBox.classList.add(messageData['level'], 'notification'); + notificationDiv.appendChild(messageBox); + if (messageStatus === 'message:download') { + checkDownloadIcons(); + } + } + // reload page when no more notifications + if (nots > 0 && messages.length === 0) { + location.reload(); + } + return messages; } // check if download icons are needed function checkDownloadIcons() { - var iconBox = document.getElementById("downloadControl"); - if (iconBox.childElementCount === 0) { - var downloadIcons = buildDownloadIcons(); - iconBox.appendChild(downloadIcons); - }; + let iconBox = document.getElementById('downloadControl'); + if (iconBox.childElementCount === 0) { + let downloadIcons = buildDownloadIcons(); + iconBox.appendChild(downloadIcons); + } } // add dl control icons function buildDownloadIcons() { - var downloadIcons = document.createElement('div'); - downloadIcons.classList = 'dl-control-icons'; - // stop icon - var stopIcon = document.createElement('img'); - stopIcon.setAttribute('id', "stop-icon"); - stopIcon.setAttribute('title', "Stop Download Queue"); - stopIcon.setAttribute('src', "/static/img/icon-stop.svg"); - stopIcon.setAttribute('alt', "stop icon"); - stopIcon.setAttribute('onclick', 'stopQueue()'); - // kill icon - var killIcon = document.createElement('img'); - killIcon.setAttribute('id', "kill-icon"); - killIcon.setAttribute('title', "Kill Download Queue"); - killIcon.setAttribute('src', "/static/img/icon-close.svg"); - killIcon.setAttribute('alt', "kill icon"); - killIcon.setAttribute('onclick', 'killQueue()'); - // stich together - downloadIcons.appendChild(stopIcon); - downloadIcons.appendChild(killIcon); - return downloadIcons + let downloadIcons = document.createElement('div'); + downloadIcons.classList = 'dl-control-icons'; + // stop icon + let stopIcon = document.createElement('img'); + stopIcon.setAttribute('id', 'stop-icon'); + stopIcon.setAttribute('title', 'Stop Download Queue'); + stopIcon.setAttribute('src', '/static/img/icon-stop.svg'); + stopIcon.setAttribute('alt', 'stop icon'); + stopIcon.setAttribute('onclick', 'stopQueue()'); + // kill icon + let killIcon = document.createElement('img'); + killIcon.setAttribute('id', 'kill-icon'); + killIcon.setAttribute('title', 'Kill Download Queue'); + killIcon.setAttribute('src', '/static/img/icon-close.svg'); + killIcon.setAttribute('alt', 'kill icon'); + killIcon.setAttribute('onclick', 'killQueue()'); + // stich together + downloadIcons.appendChild(stopIcon); + downloadIcons.appendChild(killIcon); + return downloadIcons; } diff --git a/tubearchivist/static/script.js b/tubearchivist/static/script.js index d680b06..e68971b 100644 --- a/tubearchivist/static/script.js +++ b/tubearchivist/static/script.js @@ -1,53 +1,59 @@ +'use strict'; + +/* globals checkMessages */ + function sortChange(sortValue) { - var payload = JSON.stringify({'sort_order': sortValue}); - sendPost(payload); - setTimeout(function(){ - location.reload(); - return false; - }, 500); + let payload = JSON.stringify({ sort_order: sortValue }); + sendPost(payload); + setTimeout(function () { + location.reload(); + }, 500); } // Updates video watch status when passed a video id and it's current state (ex if the video was unwatched but you want to mark it as watched you will pass "unwatched") function updateVideoWatchStatus(input1, videoCurrentWatchStatus) { - if (videoCurrentWatchStatus) { - videoId = input1; - } else if (input1.getAttribute("data-id")) { - videoId = input1.getAttribute("data-id"); - videoCurrentWatchStatus = input1.getAttribute("data-status"); - } + let videoId; + if (videoCurrentWatchStatus) { + videoId = input1; + } else if (input1.getAttribute('data-id')) { + videoId = input1.getAttribute('data-id'); + videoCurrentWatchStatus = input1.getAttribute('data-status'); + } - postVideoProgress(videoId, 0); // Reset video progress on watched/unwatched; - removeProgressBar(videoId); + postVideoProgress(videoId, 0); // Reset video progress on watched/unwatched; + removeProgressBar(videoId); - if (videoCurrentWatchStatus == "watched") { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); - var payload = JSON.stringify({'un_watched': videoId}); - sendPost(payload); - } else if (videoCurrentWatchStatus == "unwatched") { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); - var payload = JSON.stringify({'watched': videoId}); - sendPost(payload); - } + let watchStatusIndicator, payload; + if (videoCurrentWatchStatus === 'watched') { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); + payload = JSON.stringify({ un_watched: videoId }); + sendPost(payload); + } else if (videoCurrentWatchStatus === 'unwatched') { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); + payload = JSON.stringify({ watched: videoId }); + sendPost(payload); + } - var watchButtons = document.getElementsByClassName("watch-button"); - for (let i = 0; i < watchButtons.length; i++) { - if (watchButtons[i].getAttribute("data-id") == videoId) { - watchButtons[i].outerHTML = watchStatusIndicator; - } + let watchButtons = document.getElementsByClassName('watch-button'); + for (let i = 0; i < watchButtons.length; i++) { + if (watchButtons[i].getAttribute('data-id') === videoId) { + watchButtons[i].outerHTML = watchStatusIndicator; } + } } // Creates a watch status indicator when passed a video id and the videos watch status function createWatchStatusIndicator(videoId, videoWatchStatus) { - if (videoWatchStatus == "watched") { - var seen = "seen"; - var title = "Mark as unwatched"; - } else if (videoWatchStatus == "unwatched") { - var seen = "unseen"; - var title = "Mark as watched"; - } - var watchStatusIndicator = `${seen}-icon`; - return watchStatusIndicator; + let seen, title; + if (videoWatchStatus === 'watched') { + seen = 'seen'; + title = 'Mark as unwatched'; + } else if (videoWatchStatus === 'unwatched') { + seen = 'unseen'; + title = 'Mark as watched'; + } + let watchStatusIndicator = `${seen}-icon`; + return watchStatusIndicator; } // function isWatched(youtube_id) { @@ -65,18 +71,17 @@ function createWatchStatusIndicator(videoId, videoWatchStatus) { // Removes the progress bar when passed a video id function removeProgressBar(videoId) { - setProgressBar(videoId, 0, 1); + setProgressBar(videoId, 0, 1); } function isWatchedButton(button) { - youtube_id = button.getAttribute("data-id"); - var payload = JSON.stringify({'watched': youtube_id}); - button.remove(); - sendPost(payload); - setTimeout(function(){ - location.reload(); - return false; - }, 1000); + let youtube_id = button.getAttribute('data-id'); + let payload = JSON.stringify({ watched: youtube_id }); + button.remove(); + sendPost(payload); + setTimeout(function () { + location.reload(); + }, 1000); } // function isUnwatched(youtube_id) { @@ -94,336 +99,331 @@ function isWatchedButton(button) { // } function unsubscribe(id_unsub) { - var payload = JSON.stringify({'unsubscribe': id_unsub}); - sendPost(payload); - var message = document.createElement('span'); - message.innerText = "You are unsubscribed."; - document.getElementById(id_unsub).replaceWith(message); + let payload = JSON.stringify({ unsubscribe: id_unsub }); + sendPost(payload); + let message = document.createElement('span'); + message.innerText = 'You are unsubscribed.'; + document.getElementById(id_unsub).replaceWith(message); } function subscribe(id_sub) { - var payload = JSON.stringify({'subscribe': id_sub}); - sendPost(payload); - var message = document.createElement('span'); - message.innerText = "You are subscribed."; - document.getElementById(id_sub).replaceWith(message); + let payload = JSON.stringify({ subscribe: id_sub }); + sendPost(payload); + let message = document.createElement('span'); + message.innerText = 'You are subscribed.'; + document.getElementById(id_sub).replaceWith(message); } function changeView(image) { - var sourcePage = image.getAttribute("data-origin"); - var newView = image.getAttribute("data-value"); - var payload = JSON.stringify({'change_view': sourcePage + ":" + newView}); - sendPost(payload); - setTimeout(function(){ - location.reload(); - return false; - }, 500); + let sourcePage = image.getAttribute('data-origin'); + let newView = image.getAttribute('data-value'); + let payload = JSON.stringify({ change_view: sourcePage + ':' + newView }); + sendPost(payload); + setTimeout(function () { + location.reload(); + }, 500); } function changeGridItems(image) { - var operator = image.getAttribute("data-value"); - var payload = JSON.stringify({'change_grid': operator}); - sendPost(payload); - setTimeout(function(){ - location.reload(); - return false; - }, 500); + let operator = image.getAttribute('data-value'); + let payload = JSON.stringify({ change_grid: operator }); + sendPost(payload); + setTimeout(function () { + location.reload(); + }, 500); } function toggleCheckbox(checkbox) { - // pass checkbox id as key and checkbox.checked as value - var toggleId = checkbox.id; - var toggleVal = checkbox.checked; - var payloadDict = {}; - payloadDict[toggleId] = toggleVal; - var payload = JSON.stringify(payloadDict); - sendPost(payload); - setTimeout(function(){ - var currPage = window.location.pathname + window.location.search; - window.location.replace(currPage); - return false; - }, 500); + // pass checkbox id as key and checkbox.checked as value + let toggleId = checkbox.id; + let toggleVal = checkbox.checked; + let payloadDict = {}; + payloadDict[toggleId] = toggleVal; + let payload = JSON.stringify(payloadDict); + sendPost(payload); + setTimeout(function () { + let currPage = window.location.pathname + window.location.search; + window.location.replace(currPage); + }, 500); } // download page buttons function rescanPending() { - var payload = JSON.stringify({'rescan_pending': true}); - animate('rescan-icon', 'rotate-img'); - sendPost(payload); - setTimeout(function(){ - checkMessages(); - }, 500); + let payload = JSON.stringify({ rescan_pending: true }); + animate('rescan-icon', 'rotate-img'); + sendPost(payload); + setTimeout(function () { + checkMessages(); + }, 500); } function dlPending() { - var payload = JSON.stringify({'dl_pending': true}); - animate('download-icon', 'bounce-img'); - sendPost(payload); - setTimeout(function(){ - checkMessages(); - }, 500); + let payload = JSON.stringify({ dl_pending: true }); + animate('download-icon', 'bounce-img'); + sendPost(payload); + setTimeout(function () { + checkMessages(); + }, 500); } function toIgnore(button) { - var youtube_id = button.getAttribute('data-id'); - var payload = JSON.stringify({'ignore': youtube_id}); - sendPost(payload); - document.getElementById('dl-' + youtube_id).remove(); + let youtube_id = button.getAttribute('data-id'); + let payload = JSON.stringify({ ignore: youtube_id }); + sendPost(payload); + document.getElementById('dl-' + youtube_id).remove(); } function downloadNow(button) { - var youtube_id = button.getAttribute('data-id'); - var payload = JSON.stringify({'dlnow': youtube_id}); - sendPost(payload); - document.getElementById(youtube_id).remove(); - setTimeout(function(){ - checkMessages(); - }, 500); + let youtube_id = button.getAttribute('data-id'); + let payload = JSON.stringify({ dlnow: youtube_id }); + sendPost(payload); + document.getElementById(youtube_id).remove(); + setTimeout(function () { + checkMessages(); + }, 500); } function forgetIgnore(button) { - var youtube_id = button.getAttribute('data-id'); - var payload = JSON.stringify({'forgetIgnore': youtube_id}); - sendPost(payload); - document.getElementById("dl-" + youtube_id).remove(); + let youtube_id = button.getAttribute('data-id'); + let payload = JSON.stringify({ forgetIgnore: youtube_id }); + sendPost(payload); + document.getElementById('dl-' + youtube_id).remove(); } function addSingle(button) { - var youtube_id = button.getAttribute('data-id'); - var payload = JSON.stringify({'addSingle': youtube_id}); - sendPost(payload); - document.getElementById("dl-" + youtube_id).remove(); - setTimeout(function(){ - checkMessages(); - }, 500); + let youtube_id = button.getAttribute('data-id'); + let payload = JSON.stringify({ addSingle: youtube_id }); + sendPost(payload); + document.getElementById('dl-' + youtube_id).remove(); + setTimeout(function () { + checkMessages(); + }, 500); } function deleteQueue(button) { - var to_delete = button.getAttribute('data-id'); - var payload = JSON.stringify({'deleteQueue': to_delete}); - sendPost(payload); - // clear button - var message = document.createElement('p'); - message.innerText = 'deleting download queue: ' + to_delete; - document.getElementById(button.id).replaceWith(message); + let to_delete = button.getAttribute('data-id'); + let payload = JSON.stringify({ deleteQueue: to_delete }); + sendPost(payload); + // clear button + let message = document.createElement('p'); + message.innerText = 'deleting download queue: ' + to_delete; + document.getElementById(button.id).replaceWith(message); } function stopQueue() { - var payload = JSON.stringify({'queue': 'stop'}); - sendPost(payload); - document.getElementById('stop-icon').remove(); + let payload = JSON.stringify({ queue: 'stop' }); + sendPost(payload); + document.getElementById('stop-icon').remove(); } function killQueue() { - var payload = JSON.stringify({'queue': 'kill'}); - sendPost(payload); - document.getElementById('kill-icon').remove(); + let payload = JSON.stringify({ queue: 'kill' }); + sendPost(payload); + document.getElementById('kill-icon').remove(); } // settings page buttons function manualImport() { - var payload = JSON.stringify({'manual-import': true}); - sendPost(payload); - // clear button - var message = document.createElement('p'); - message.innerText = 'processing import'; - var toReplace = document.getElementById('manual-import'); - toReplace.innerHTML = ''; - toReplace.appendChild(message); + let payload = JSON.stringify({ 'manual-import': true }); + sendPost(payload); + // clear button + let message = document.createElement('p'); + message.innerText = 'processing import'; + let toReplace = document.getElementById('manual-import'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); } function reEmbed() { - var payload = JSON.stringify({'re-embed': true}); - sendPost(payload); - // clear button - var message = document.createElement('p'); - message.innerText = 'processing thumbnails'; - var toReplace = document.getElementById('re-embed'); - toReplace.innerHTML = ''; - toReplace.appendChild(message); + let payload = JSON.stringify({ 're-embed': true }); + sendPost(payload); + // clear button + let message = document.createElement('p'); + message.innerText = 'processing thumbnails'; + let toReplace = document.getElementById('re-embed'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); } function dbBackup() { - var payload = JSON.stringify({'db-backup': true}); - sendPost(payload); - // clear button - var message = document.createElement('p'); - message.innerText = 'backing up archive'; - var toReplace = document.getElementById('db-backup'); - toReplace.innerHTML = ''; - toReplace.appendChild(message); + let payload = JSON.stringify({ 'db-backup': true }); + sendPost(payload); + // clear button + let message = document.createElement('p'); + message.innerText = 'backing up archive'; + let toReplace = document.getElementById('db-backup'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); } function dbRestore(button) { - var fileName = button.getAttribute("data-id"); - var payload = JSON.stringify({'db-restore': fileName}); - sendPost(payload); - // clear backup row - var message = document.createElement('p'); - message.innerText = 'restoring from backup'; - var toReplace = document.getElementById(fileName); - toReplace.innerHTML = ''; - toReplace.appendChild(message); + let fileName = button.getAttribute('data-id'); + let payload = JSON.stringify({ 'db-restore': fileName }); + sendPost(payload); + // clear backup row + let message = document.createElement('p'); + message.innerText = 'restoring from backup'; + let toReplace = document.getElementById(fileName); + toReplace.innerHTML = ''; + toReplace.appendChild(message); } function fsRescan() { - var payload = JSON.stringify({'fs-rescan': true}); - sendPost(payload); - // clear button - var message = document.createElement('p'); - message.innerText = 'File system scan in progress'; - var toReplace = document.getElementById('fs-rescan'); - toReplace.innerHTML = ''; - toReplace.appendChild(message); + let payload = JSON.stringify({ 'fs-rescan': true }); + sendPost(payload); + // clear button + let message = document.createElement('p'); + message.innerText = 'File system scan in progress'; + let toReplace = document.getElementById('fs-rescan'); + toReplace.innerHTML = ''; + toReplace.appendChild(message); } function resetToken() { - var payload = JSON.stringify({'reset-token': true}); - sendPost(payload); - var message = document.createElement("p"); - message.innerText = "Token revoked"; - document.getElementById("text-reveal").replaceWith(message); + let payload = JSON.stringify({ 'reset-token': true }); + sendPost(payload); + let message = document.createElement('p'); + message.innerText = 'Token revoked'; + document.getElementById('text-reveal').replaceWith(message); } // delete from file system function deleteConfirm() { - to_show = document.getElementById("delete-button"); - document.getElementById("delete-item").style.display = 'none'; - to_show.style.display = "block"; + let to_show = document.getElementById('delete-button'); + document.getElementById('delete-item').style.display = 'none'; + to_show.style.display = 'block'; } function deleteVideo(button) { - var to_delete = button.getAttribute("data-id"); - var to_redirect = button.getAttribute("data-redirect"); - var payload = JSON.stringify({"delete-video": to_delete}); - sendPost(payload); - setTimeout(function(){ - var redirect = "/channel/" + to_redirect; - window.location.replace(redirect); - return false; - }, 1000); + let to_delete = button.getAttribute('data-id'); + let to_redirect = button.getAttribute('data-redirect'); + let payload = JSON.stringify({ 'delete-video': to_delete }); + sendPost(payload); + setTimeout(function () { + let redirect = '/channel/' + to_redirect; + window.location.replace(redirect); + }, 1000); } function deleteChannel(button) { - var to_delete = button.getAttribute("data-id"); - var payload = JSON.stringify({"delete-channel": to_delete}); - sendPost(payload); - setTimeout(function(){ - window.location.replace("/channel/"); - return false; - }, 1000); + let to_delete = button.getAttribute('data-id'); + let payload = JSON.stringify({ 'delete-channel': to_delete }); + sendPost(payload); + setTimeout(function () { + window.location.replace('/channel/'); + }, 1000); } function deletePlaylist(button) { - var playlist_id = button.getAttribute("data-id"); - var playlist_action = button.getAttribute("data-action"); - var payload = JSON.stringify({ - "delete-playlist": { - "playlist-id": playlist_id, - "playlist-action": playlist_action - } - }); - sendPost(payload); - setTimeout(function(){ - window.location.replace("/playlist/"); - return false; - }, 1000); + let playlist_id = button.getAttribute('data-id'); + let playlist_action = button.getAttribute('data-action'); + let payload = JSON.stringify({ + 'delete-playlist': { + 'playlist-id': playlist_id, + 'playlist-action': playlist_action, + }, + }); + sendPost(payload); + setTimeout(function () { + window.location.replace('/playlist/'); + }, 1000); } function cancelDelete() { - document.getElementById("delete-button").style.display = 'none'; - document.getElementById("delete-item").style.display = 'block'; + document.getElementById('delete-button').style.display = 'none'; + document.getElementById('delete-item').style.display = 'block'; } // get seconds from hh:mm:ss.ms timestamp function getSeconds(timestamp) { - var elements = timestamp.split(":", 3); - var secs = parseInt(elements[0]) * 60 * 60 + parseInt(elements[1]) * 60 + parseFloat(elements[2]) - return secs + let elements = timestamp.split(':', 3); + let secs = parseInt(elements[0]) * 60 * 60 + parseInt(elements[1]) * 60 + parseFloat(elements[2]); + return secs; } // player -var sponsorBlock = []; +let sponsorBlock = []; function createPlayer(button) { - var videoId = button.getAttribute('data-id'); - var videoPosition = button.getAttribute('data-position'); - var videoData = getVideoData(videoId); + let videoId = button.getAttribute('data-id'); + let videoPosition = button.getAttribute('data-position'); + let videoData = getVideoData(videoId); - var sponsorBlockElements = ''; - if (videoData.data.sponsorblock && videoData.data.sponsorblock.is_enabled) { - sponsorBlock = videoData.data.sponsorblock; - if (sponsorBlock.segments.length == 0) { - sponsorBlockElements = ` + let sponsorBlockElements = ''; + if (videoData.data.sponsorblock && videoData.data.sponsorblock.is_enabled) { + sponsorBlock = videoData.data.sponsorblock; + if (sponsorBlock.segments.length === 0) { + sponsorBlockElements = `

This video doesn't have any sponsor segments added. To add a segment go to this video on Youtube and add a segment using the SponsorBlock extension.

`; - } else { - if (sponsorBlock.has_unlocked) { - sponsorBlockElements = ` + } else { + if (sponsorBlock.has_unlocked) { + sponsorBlockElements = `

This video has unlocked sponsor segments. Go to this video on YouTube and vote on the segments using the SponsorBlock extension.

`; - } - } - } else { - sponsorBlock = null; + } } - if (videoPosition) { - var videoProgress = getSeconds(videoPosition) - } else { - var videoProgress = getVideoProgress(videoId).position; + } else { + sponsorBlock = null; + } + let videoProgress; + if (videoPosition) { + videoProgress = getSeconds(videoPosition); + } else { + videoProgress = getVideoProgress(videoId).position; + } + let videoName = videoData.data.title; + + let videoTag = createVideoTag(videoData, videoProgress); + + let playlist = ''; + let videoPlaylists = videoData.data.playlist; // Array of playlists the video is in + if (typeof videoPlaylists !== 'undefined') { + let subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed + if (subbedPlaylists.length !== 0) { + let playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist + let playlistId = playlistData.playlist_id; + let playlistName = playlistData.playlist_name; + playlist = `
${playlistName}
`; } - var videoName = videoData.data.title; + } - var videoTag = createVideoTag(videoData, videoProgress); + let videoViews = formatNumbers(videoData.data.stats.view_count); - var playlist = ''; - var videoPlaylists = videoData.data.playlist; // Array of playlists the video is in - if (typeof(videoPlaylists) != 'undefined') { - var subbedPlaylists = getSubbedPlaylists(videoPlaylists); // Array of playlist the video is in that are subscribed - if (subbedPlaylists.length != 0) { - var playlistData = getPlaylistData(subbedPlaylists[0]); // Playlist data for first subscribed playlist - var playlistId = playlistData.playlist_id; - var playlistName = playlistData.playlist_name; - var playlist = `
${playlistName}
`; - } - } + let channelId = videoData.data.channel.channel_id; + let channelName = videoData.data.channel.channel_name; - var videoViews = formatNumbers(videoData.data.stats.view_count); + removePlayer(); - var channelId = videoData.data.channel.channel_id; - var channelName = videoData.data.channel.channel_name; + // If cast integration is enabled create cast button + let castButton = ''; + if (videoData.config.application.enable_cast) { + castButton = ``; + } - removePlayer(); + // Watched indicator + let watchStatusIndicator; + if (videoData.data.player.watched) { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); + } else { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); + } - // If cast integration is enabled create cast button - var castButton = ''; - if (videoData.config.application.enable_cast) { - var castButton = ``; - } + let playerStats = `
views icon${videoViews}`; + if (videoData.data.stats.like_count) { + let likes = formatNumbers(videoData.data.stats.like_count); + playerStats += `|thumbs-up${likes}`; + } + if (videoData.data.stats.dislike_count && videoData.config.downloads.integrate_ryd) { + let dislikes = formatNumbers(videoData.data.stats.dislike_count); + playerStats += `|thumbs-down${dislikes}`; + } + playerStats += '
'; - // Watched indicator - if (videoData.data.player.watched) { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); - } else { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); - } - - - var playerStats = `
views icon${videoViews}`; - if (videoData.data.stats.like_count) { - var likes = formatNumbers(videoData.data.stats.like_count); - playerStats += `|thumbs-up${likes}`; - } - if (videoData.data.stats.dislike_count && videoData.config.downloads.integrate_ryd) { - var dislikes = formatNumbers(videoData.data.stats.dislike_count); - playerStats += `|thumbs-down${dislikes}`; - } - playerStats += "
"; - - const markup = ` + const markup = `
${videoTag} @@ -442,486 +442,510 @@ function createPlayer(button) {
`; - const divPlayer = document.getElementById("player"); - divPlayer.innerHTML = markup; - recordTextTrackChanges(); + const divPlayer = document.getElementById('player'); + divPlayer.innerHTML = markup; + recordTextTrackChanges(); } // Add video tag to video page when passed a video id, function loaded on page load `video.html (115-117)` function insertVideoTag(videoData, videoProgress) { - var videoTag = createVideoTag(videoData, videoProgress); - var videoMain = document.querySelector(".video-main"); - videoMain.innerHTML += videoTag; + let videoTag = createVideoTag(videoData, videoProgress); + let videoMain = document.querySelector('.video-main'); + videoMain.innerHTML += videoTag; } // Generates a video tag with subtitles when passed videoData and videoProgress. function createVideoTag(videoData, videoProgress) { - var videoId = videoData.data.youtube_id; - var videoUrl = videoData.data.media_url; - var videoThumbUrl = videoData.data.vid_thumb_url; - var subtitles = ''; - var videoSubtitles = videoData.data.subtitles; // Array of subtitles - if (typeof(videoSubtitles) != 'undefined' && videoData.config.downloads.subtitle) { - for (var i = 0; i < videoSubtitles.length; i++) { - let label = videoSubtitles[i].name; - if (videoSubtitles[i].source == "auto") { - label += " - auto"; - } - subtitles += ``; - } + let videoId = videoData.data.youtube_id; + let videoUrl = videoData.data.media_url; + let videoThumbUrl = videoData.data.vid_thumb_url; + let subtitles = ''; + let videoSubtitles = videoData.data.subtitles; // Array of subtitles + if (typeof videoSubtitles !== 'undefined' && videoData.config.downloads.subtitle) { + for (let i = 0; i < videoSubtitles.length; i++) { + let label = videoSubtitles[i].name; + if (videoSubtitles[i].source === 'auto') { + label += ' - auto'; + } + subtitles += ``; } + } - var videoTag = ` + let videoTag = ` `; - return videoTag; + return videoTag; } // Gets video tag function getVideoPlayer() { - var videoElement = document.getElementById("video-item"); - return videoElement; + let videoElement = document.getElementById('video-item'); + return videoElement; } // Gets the video source tag function getVideoPlayerVideoSource() { - var videoPlayerVideoSource = document.getElementById("video-source"); - return videoPlayerVideoSource; + let videoPlayerVideoSource = document.getElementById('video-source'); + return videoPlayerVideoSource; } // Gets the current progress of the video currently in the player function getVideoPlayerCurrentTime() { - var videoElement = getVideoPlayer(); - if (videoElement != null) { - return videoElement.currentTime; - } + let videoElement = getVideoPlayer(); + if (videoElement != null) { + return videoElement.currentTime; + } } // Gets the video id of the video currently in the player function getVideoPlayerVideoId() { - var videoPlayerVideoSource = getVideoPlayerVideoSource(); - if (videoPlayerVideoSource != null) { - return videoPlayerVideoSource.getAttribute("videoid"); - } + let videoPlayerVideoSource = getVideoPlayerVideoSource(); + if (videoPlayerVideoSource != null) { + return videoPlayerVideoSource.getAttribute('videoid'); + } } // Gets the duration of the video currently in the player function getVideoPlayerDuration() { - var videoElement = getVideoPlayer(); - if (videoElement != null) { - return videoElement.duration; - } + let videoElement = getVideoPlayer(); + if (videoElement != null) { + return videoElement.duration; + } } // Gets current watch status of video based on watch button function getVideoPlayerWatchStatus() { - var videoId = getVideoPlayerVideoId(); - var watched = false; + let videoId = getVideoPlayerVideoId(); + let watched = false; - var watchButtons = document.getElementsByClassName("watch-button"); - for (let i = 0; i < watchButtons.length; i++) { - if (watchButtons[i].getAttribute("data-id") == videoId && watchButtons[i].getAttribute("data-status") == "watched") { - watched = true; - } + let watchButtons = document.getElementsByClassName('watch-button'); + for (let i = 0; i < watchButtons.length; i++) { + if ( + watchButtons[i].getAttribute('data-id') === videoId && + watchButtons[i].getAttribute('data-status') === 'watched' + ) { + watched = true; } - return watched; + } + return watched; } // Runs on video playback, marks video as watched if video gets to 90% or higher, sends position to api, SB skipping function onVideoProgress() { - var videoId = getVideoPlayerVideoId(); - var currentTime = getVideoPlayerCurrentTime(); - var duration = getVideoPlayerDuration(); - var videoElement = getVideoPlayer(); - var notificationsElement = document.getElementById("notifications"); - if (sponsorBlock && sponsorBlock.segments) { - for(let i in sponsorBlock.segments) { - if(currentTime >= sponsorBlock.segments[i].segment[0] && currentTime <= sponsorBlock.segments[i].segment[0] + 0.3) { - videoElement.currentTime = sponsorBlock.segments[i].segment[1]; - var notificationElement = document.getElementById("notification-" + sponsorBlock.segments[i].UUID); - if (!notificationElement) { - notificationsElement.innerHTML += `

Skipped sponsor segment from ${formatTime(sponsorBlock.segments[i].segment[0])} to ${formatTime(sponsorBlock.segments[i].segment[1])}.

`; - } - } - if(currentTime > sponsorBlock.segments[i].segment[1] + 10) { - var notificationsElementUUID = document.getElementById("notification-" + sponsorBlock.segments[i].UUID); - if(notificationsElementUUID) { - notificationsElementUUID.outerHTML = ''; - } - } + let videoId = getVideoPlayerVideoId(); + let currentTime = getVideoPlayerCurrentTime(); + let duration = getVideoPlayerDuration(); + let videoElement = getVideoPlayer(); + let notificationsElement = document.getElementById('notifications'); + if (sponsorBlock && sponsorBlock.segments) { + for (let i in sponsorBlock.segments) { + if ( + currentTime >= sponsorBlock.segments[i].segment[0] && + currentTime <= sponsorBlock.segments[i].segment[0] + 0.3 + ) { + videoElement.currentTime = sponsorBlock.segments[i].segment[1]; + let notificationElement = document.getElementById( + 'notification-' + sponsorBlock.segments[i].UUID + ); + if (!notificationElement) { + notificationsElement.innerHTML += `

Skipped sponsor segment from ${formatTime( + sponsorBlock.segments[i].segment[0] + )} to ${formatTime(sponsorBlock.segments[i].segment[1])}.

`; } - } - if ((currentTime % 10).toFixed(1) <= 0.2) { // Check progress every 10 seconds or else progress is checked a few times a second - postVideoProgress(videoId, currentTime); - if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched - if (watchedThreshold(currentTime, duration)) { - updateVideoWatchStatus(videoId, "unwatched"); - } + } + if (currentTime > sponsorBlock.segments[i].segment[1] + 10) { + let notificationsElementUUID = document.getElementById( + 'notification-' + sponsorBlock.segments[i].UUID + ); + if (notificationsElementUUID) { + notificationsElementUUID.outerHTML = ''; } + } } + } + if ((currentTime % 10).toFixed(1) <= 0.2) { + // Check progress every 10 seconds or else progress is checked a few times a second + postVideoProgress(videoId, currentTime); + if (!getVideoPlayerWatchStatus()) { + // Check if video is already marked as watched + if (watchedThreshold(currentTime, duration)) { + updateVideoWatchStatus(videoId, 'unwatched'); + } + } + } } // Runs on video end, marks video as watched function onVideoEnded() { - var videoId = getVideoPlayerVideoId(); - if (!getVideoPlayerWatchStatus()) { // Check if video is already marked as watched - updateVideoWatchStatus(videoId, "unwatched"); - } - for(let i in sponsorBlock.segments) { - var notificationsElementUUID = document.getElementById("notification-" + sponsorBlock.segments[i].UUID); - if(notificationsElementUUID) { - notificationsElementUUID.outerHTML = ''; - } + let videoId = getVideoPlayerVideoId(); + if (!getVideoPlayerWatchStatus()) { + // Check if video is already marked as watched + updateVideoWatchStatus(videoId, 'unwatched'); + } + for (let i in sponsorBlock.segments) { + let notificationsElementUUID = document.getElementById( + 'notification-' + sponsorBlock.segments[i].UUID + ); + if (notificationsElementUUID) { + notificationsElementUUID.outerHTML = ''; } + } } function watchedThreshold(currentTime, duration) { - var watched = false; - if (duration <= 1800){ // If video is less than 30 min - if ((currentTime / duration) >= 0.90) { // Mark as watched at 90% - var watched = true; - } - } else { // If video is more than 30 min - if (currentTime >= (duration - 120)) { // Mark as watched if there is two minutes left - var watched = true; - } + let watched = false; + if (duration <= 1800) { + // If video is less than 30 min + if (currentTime / duration >= 0.9) { + // Mark as watched at 90% + watched = true; } - return watched; + } else { + // If video is more than 30 min + if (currentTime >= duration - 120) { + // Mark as watched if there is two minutes left + watched = true; + } + } + return watched; } // Runs on video pause. Sends current position. function onVideoPause() { - var videoId = getVideoPlayerVideoId(); - var currentTime = getVideoPlayerCurrentTime(); - postVideoProgress(videoId, currentTime); + let videoId = getVideoPlayerVideoId(); + let currentTime = getVideoPlayerCurrentTime(); + postVideoProgress(videoId, currentTime); } // Format numbers for frontend function formatNumbers(number) { - var numberUnformatted = parseFloat(number); - if (numberUnformatted > 999999999) { - var numberFormatted = (numberUnformatted / 1000000000).toFixed(1).toString() + "B"; - } else if (numberUnformatted > 999999) { - var numberFormatted = (numberUnformatted / 1000000).toFixed(1).toString() + "M"; - } else if (numberUnformatted > 999) { - var numberFormatted = (numberUnformatted / 1000).toFixed(1).toString() + "K"; - } else { - var numberFormatted = numberUnformatted; - } - return numberFormatted; + let numberUnformatted = parseFloat(number); + let numberFormatted; + if (numberUnformatted > 999999999) { + numberFormatted = (numberUnformatted / 1000000000).toFixed(1).toString() + 'B'; + } else if (numberUnformatted > 999999) { + numberFormatted = (numberUnformatted / 1000000).toFixed(1).toString() + 'M'; + } else if (numberUnformatted > 999) { + numberFormatted = (numberUnformatted / 1000).toFixed(1).toString() + 'K'; + } else { + numberFormatted = numberUnformatted; + } + return numberFormatted; } // Formats times in seconds for frontend function formatTime(time) { - var hoursUnformatted = time / 3600; - var minutesUnformatted = (time % 3600) / 60; - var secondsUnformatted = time % 60; + let hoursUnformatted = time / 3600; + let minutesUnformatted = (time % 3600) / 60; + let secondsUnformatted = time % 60; - var hoursFormatted = Math.trunc(hoursUnformatted); - if(minutesUnformatted < 10 && hoursFormatted > 0) { - var minutesFormatted = "0" + Math.trunc(minutesUnformatted); - } else { - var minutesFormatted = Math.trunc(minutesUnformatted); - } - if(secondsUnformatted < 10) { - var secondsFormatted = "0" + Math.trunc(secondsUnformatted); - } else { - var secondsFormatted = Math.trunc(secondsUnformatted); - } + let hoursFormatted = Math.trunc(hoursUnformatted); + let minutesFormatted; + if (minutesUnformatted < 10 && hoursFormatted > 0) { + minutesFormatted = '0' + Math.trunc(minutesUnformatted); + } else { + minutesFormatted = Math.trunc(minutesUnformatted); + } + let secondsFormatted; + if (secondsUnformatted < 10) { + secondsFormatted = '0' + Math.trunc(secondsUnformatted); + } else { + secondsFormatted = Math.trunc(secondsUnformatted); + } - var timeUnformatted = ''; - if(hoursFormatted > 0) { - timeUnformatted = hoursFormatted + ":" - } - var timeFormatted = timeUnformatted.concat(minutesFormatted, ":", secondsFormatted); - return timeFormatted; + let timeUnformatted = ''; + if (hoursFormatted > 0) { + timeUnformatted = hoursFormatted + ':'; + } + let timeFormatted = timeUnformatted.concat(minutesFormatted, ':', secondsFormatted); + return timeFormatted; } // Gets video data when passed video ID function getVideoData(videoId) { - var apiEndpoint = "/api/video/" + videoId + "/"; - var videoData = apiRequest(apiEndpoint, "GET"); - return videoData; + let apiEndpoint = '/api/video/' + videoId + '/'; + let videoData = apiRequest(apiEndpoint, 'GET'); + return videoData; } // Gets channel data when passed channel ID function getChannelData(channelId) { - var apiEndpoint = "/api/channel/" + channelId + "/"; - var channelData = apiRequest(apiEndpoint, "GET"); - return channelData.data; + let apiEndpoint = '/api/channel/' + channelId + '/'; + let channelData = apiRequest(apiEndpoint, 'GET'); + return channelData.data; } // Gets playlist data when passed playlist ID function getPlaylistData(playlistId) { - var apiEndpoint = "/api/playlist/" + playlistId + "/"; - var playlistData = apiRequest(apiEndpoint, "GET"); - return playlistData.data; + let apiEndpoint = '/api/playlist/' + playlistId + '/'; + let playlistData = apiRequest(apiEndpoint, 'GET'); + return playlistData.data; } // Get video progress data when passed video ID function getVideoProgress(videoId) { - var apiEndpoint = "/api/video/" + videoId + "/progress/"; - var videoProgress = apiRequest(apiEndpoint, "GET"); - return videoProgress; + let apiEndpoint = '/api/video/' + videoId + '/progress/'; + let videoProgress = apiRequest(apiEndpoint, 'GET'); + return videoProgress; } // Given an array of playlist ids it returns an array of subbed playlist ids from that list function getSubbedPlaylists(videoPlaylists) { - var subbedPlaylists = []; - for (var i = 0; i < videoPlaylists.length; i++) { - if(getPlaylistData(videoPlaylists[i]).playlist_subscribed) { - subbedPlaylists.push(videoPlaylists[i]); - } + let subbedPlaylists = []; + for (let i = 0; i < videoPlaylists.length; i++) { + if (getPlaylistData(videoPlaylists[i]).playlist_subscribed) { + subbedPlaylists.push(videoPlaylists[i]); } - return subbedPlaylists; + } + return subbedPlaylists; } // Send video position when given video id and progress in seconds function postVideoProgress(videoId, videoProgress) { - var apiEndpoint = "/api/video/" + videoId + "/progress/"; - var duartion = getVideoPlayerDuration(); - if (!isNaN(videoProgress) && duartion != 'undefined') { - var data = { - "position": videoProgress - }; - if (videoProgress == 0) { - apiRequest(apiEndpoint, "DELETE"); - // console.log("Deleting Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); - } else if (!getVideoPlayerWatchStatus()) { - apiRequest(apiEndpoint, "POST", data); - // console.log("Saving Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); - } + let apiEndpoint = '/api/video/' + videoId + '/progress/'; + let duartion = getVideoPlayerDuration(); + if (!isNaN(videoProgress) && duartion !== 'undefined') { + let data = { + position: videoProgress, + }; + if (videoProgress === 0) { + apiRequest(apiEndpoint, 'DELETE'); + // console.log("Deleting Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); + } else if (!getVideoPlayerWatchStatus()) { + apiRequest(apiEndpoint, 'POST', data); + // console.log("Saving Video Progress for Video ID: " + videoId + ", Progress: " + videoProgress); } + } } // Send sponsor segment when given video id and and timestamps function postSponsorSegment(videoId, startTime, endTime) { - var apiEndpoint = "/api/video/" + videoId + "/sponsor/"; - var data = { - "segment": { - "startTime": startTime, - "endTime": endTime - } - }; - apiRequest(apiEndpoint, "POST", data); + let apiEndpoint = '/api/video/' + videoId + '/sponsor/'; + let data = { + segment: { + startTime: startTime, + endTime: endTime, + }, + }; + apiRequest(apiEndpoint, 'POST', data); } // Send sponsor segment when given video id and and timestamps function postSponsorSegmentVote(videoId, uuid, vote) { - var apiEndpoint = "/api/video/" + videoId + "/sponsor/"; - var data = { - "vote": { - "uuid": uuid, - "yourVote": vote - } - }; - apiRequest(apiEndpoint, "POST", data); + let apiEndpoint = '/api/video/' + videoId + '/sponsor/'; + let data = { + vote: { + uuid: uuid, + yourVote: vote, + }, + }; + apiRequest(apiEndpoint, 'POST', data); } function handleCookieValidate() { - document.getElementById("cookieButton").remove(); - var cookieMessageElement = document.getElementById("cookieMessage"); - cookieMessageElement.innerHTML = `Processing.`; - response = postCookieValidate(); - if (response.cookie_validated == true) { - cookieMessageElement.innerHTML = `The cookie file is valid.`; - } else { - cookieMessageElement.innerHTML = `Warning, the cookie file is invalid.`; - } + document.getElementById('cookieButton').remove(); + let cookieMessageElement = document.getElementById('cookieMessage'); + cookieMessageElement.innerHTML = `Processing.`; + let response = postCookieValidate(); + if (response.cookie_validated === true) { + cookieMessageElement.innerHTML = `The cookie file is valid.`; + } else { + cookieMessageElement.innerHTML = `Warning, the cookie file is invalid.`; + } } // Check youtube cookie settings function postCookieValidate() { - var apiEndpoint = "/api/cookie/"; - return apiRequest(apiEndpoint, "POST"); + let apiEndpoint = '/api/cookie/'; + return apiRequest(apiEndpoint, 'POST'); } // Makes api requests when passed an endpoint and method ("GET", "POST", "DELETE") function apiRequest(apiEndpoint, method, data) { - const xhttp = new XMLHttpRequest(); - var sessionToken = getCookie("sessionid"); - xhttp.open(method, apiEndpoint, false); - xhttp.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); // Used for video progress POST requests - xhttp.setRequestHeader("Authorization", "Token " + sessionToken); - xhttp.setRequestHeader("Content-Type", "application/json"); - xhttp.send(JSON.stringify(data)); - return JSON.parse(xhttp.responseText); + const xhttp = new XMLHttpRequest(); + let sessionToken = getCookie('sessionid'); + xhttp.open(method, apiEndpoint, false); + xhttp.setRequestHeader('X-CSRFToken', getCookie('csrftoken')); // Used for video progress POST requests + xhttp.setRequestHeader('Authorization', 'Token ' + sessionToken); + xhttp.setRequestHeader('Content-Type', 'application/json'); + xhttp.send(JSON.stringify(data)); + return JSON.parse(xhttp.responseText); } // Gets origin URL function getURL() { - return window.location.origin; + return window.location.origin; } function removePlayer() { - var currentTime = getVideoPlayerCurrentTime(); - var duration = getVideoPlayerDuration(); - var videoId = getVideoPlayerVideoId(); - postVideoProgress(videoId, currentTime); - setProgressBar(videoId, currentTime, duration); - var playerElement = document.getElementById('player'); - if (playerElement.hasChildNodes()) { - var youtubeId = playerElement.childNodes[1].getAttribute("data-id"); - var playedStatus = document.createDocumentFragment(); - var playedBox = document.getElementById(youtubeId); - if (playedBox) { - playedStatus.appendChild(playedBox); - } - playerElement.innerHTML = ''; - // append played status - var videoInfo = document.getElementById('video-info-' + youtubeId); - if (videoInfo) { - videoInfo.insertBefore(playedStatus, videoInfo.firstChild); - } + let currentTime = getVideoPlayerCurrentTime(); + let duration = getVideoPlayerDuration(); + let videoId = getVideoPlayerVideoId(); + postVideoProgress(videoId, currentTime); + setProgressBar(videoId, currentTime, duration); + let playerElement = document.getElementById('player'); + if (playerElement.hasChildNodes()) { + let youtubeId = playerElement.childNodes[1].getAttribute('data-id'); + let playedStatus = document.createDocumentFragment(); + let playedBox = document.getElementById(youtubeId); + if (playedBox) { + playedStatus.appendChild(playedBox); } + playerElement.innerHTML = ''; + // append played status + let videoInfo = document.getElementById('video-info-' + youtubeId); + if (videoInfo) { + videoInfo.insertBefore(playedStatus, videoInfo.firstChild); + } + } } // Sets the progress bar when passed a video id, video progress and video duration function setProgressBar(videoId, currentTime, duration) { - var progressBarWidth = (currentTime / duration) * 100 + "%"; - var progressBars = document.getElementsByClassName("video-progress-bar"); - for (let i = 0; i < progressBars.length; i++) { - if (progressBars[i].id == "progress-" + videoId) { - if (!getVideoPlayerWatchStatus()) { - progressBars[i].style.width = progressBarWidth; - } else { - progressBars[i].style.width = "0%"; - } - } + let progressBarWidth = (currentTime / duration) * 100 + '%'; + let progressBars = document.getElementsByClassName('video-progress-bar'); + for (let i = 0; i < progressBars.length; i++) { + if (progressBars[i].id === 'progress-' + videoId) { + if (!getVideoPlayerWatchStatus()) { + progressBars[i].style.width = progressBarWidth; + } else { + progressBars[i].style.width = '0%'; + } } + } - // progressBar = document.getElementById("progress-" + videoId); - - + // progressBar = document.getElementById("progress-" + videoId); } // multi search form let searchTimeout = null; function searchMulti(query) { - clearTimeout(searchTimeout); - searchTimeout = setTimeout(function () { - if (query.length > 1) { - var http = new XMLHttpRequest(); - http.onreadystatechange = function() { - if (http.readyState === 4) { - response = JSON.parse(http.response); - populateMultiSearchResults(response.results, response.queryType); - } - }; - http.open("GET", `/api/search/?query=${query}`, true); - http.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); - http.setRequestHeader("Content-type", "application/json"); - http.send(); + clearTimeout(searchTimeout); + searchTimeout = setTimeout(function () { + if (query.length > 1) { + let http = new XMLHttpRequest(); + http.onreadystatechange = function () { + if (http.readyState === 4) { + let response = JSON.parse(http.response); + populateMultiSearchResults(response.results, response.queryType); } - }, 500); + }; + http.open('GET', `/api/search/?query=${query}`, true); + http.setRequestHeader('X-CSRFToken', getCookie('csrftoken')); + http.setRequestHeader('Content-type', 'application/json'); + http.send(); + } + }, 500); } function getViewDefaults(view) { - var defaultView = document.getElementById("id_" + view).value; - return defaultView; + let defaultView = document.getElementById('id_' + view).value; + return defaultView; } function populateMultiSearchResults(allResults, queryType) { - // videos - var defaultVideo = getViewDefaults("home"); - var allVideos = allResults.video_results; - var videoBox = document.getElementById("video-results"); - videoBox.innerHTML = ""; - videoBox.parentElement.style.display = "block"; - if (allVideos.length > 0) { - for (let index = 0; index < allVideos.length; index++) { - const video = allVideos[index].source; - const videoDiv = createVideo(video, defaultVideo); - videoBox.appendChild(videoDiv); - } - } else { - if (queryType === "simple" || queryType == "video") { - videoBox.innerHTML = "

No videos found.

"; - } else { - videoBox.parentElement.style.display = "none"; - } + // videos + let defaultVideo = getViewDefaults('home'); + let allVideos = allResults.video_results; + let videoBox = document.getElementById('video-results'); + videoBox.innerHTML = ''; + videoBox.parentElement.style.display = 'block'; + if (allVideos.length > 0) { + for (let index = 0; index < allVideos.length; index++) { + const video = allVideos[index].source; + const videoDiv = createVideo(video, defaultVideo); + videoBox.appendChild(videoDiv); } - // channels - var defaultChannel = getViewDefaults("channel"); - var allChannels = allResults.channel_results; - var channelBox = document.getElementById("channel-results"); - channelBox.innerHTML = ""; - channelBox.parentElement.style.display = "block"; - if (allChannels.length > 0) { - for (let index = 0; index < allChannels.length; index++) { - const channel = allChannels[index].source; - const channelDiv = createChannel(channel, defaultChannel); - channelBox.appendChild(channelDiv); - } + } else { + if (queryType === 'simple' || queryType === 'video') { + videoBox.innerHTML = '

No videos found.

'; } else { - if (queryType === "simple" || queryType == "channel") { - channelBox.innerHTML = "

No channels found.

"; - } else { - channelBox.parentElement.style.display = "none"; - } + videoBox.parentElement.style.display = 'none'; } - // playlists - var defaultPlaylist = getViewDefaults("playlist"); - var allPlaylists = allResults.playlist_results; - var playlistBox = document.getElementById("playlist-results"); - playlistBox.innerHTML = ""; - playlistBox.parentElement.style.display = "block"; - if (allPlaylists.length > 0) { - for (let index = 0; index < allPlaylists.length; index++) { - const playlist = allPlaylists[index].source; - const playlistDiv = createPlaylist(playlist, defaultPlaylist); - playlistBox.appendChild(playlistDiv); - } + } + // channels + let defaultChannel = getViewDefaults('channel'); + let allChannels = allResults.channel_results; + let channelBox = document.getElementById('channel-results'); + channelBox.innerHTML = ''; + channelBox.parentElement.style.display = 'block'; + if (allChannels.length > 0) { + for (let index = 0; index < allChannels.length; index++) { + const channel = allChannels[index].source; + const channelDiv = createChannel(channel, defaultChannel); + channelBox.appendChild(channelDiv); + } + } else { + if (queryType === 'simple' || queryType === 'channel') { + channelBox.innerHTML = '

No channels found.

'; } else { - if (queryType === "simple" || queryType == "playlist") { - playlistBox.innerHTML = "

No playlists found.

"; - } else { - playlistBox.parentElement.style.display = "none"; - } + channelBox.parentElement.style.display = 'none'; } - // fulltext - var allFullText = allResults.fulltext_results; - var fullTextBox = document.getElementById("fulltext-results"); - fullTextBox.innerHTML = ""; - fullTextBox.parentElement.style.display = "block"; - if (allFullText.length > 0) { - for (let i = 0; i < allFullText.length; i++) { - const fullText = allFullText[i]; - if ("highlight" in fullText) { - const fullTextDiv = createFulltext(fullText); - fullTextBox.appendChild(fullTextDiv); - } - } + } + // playlists + let defaultPlaylist = getViewDefaults('playlist'); + let allPlaylists = allResults.playlist_results; + let playlistBox = document.getElementById('playlist-results'); + playlistBox.innerHTML = ''; + playlistBox.parentElement.style.display = 'block'; + if (allPlaylists.length > 0) { + for (let index = 0; index < allPlaylists.length; index++) { + const playlist = allPlaylists[index].source; + const playlistDiv = createPlaylist(playlist, defaultPlaylist); + playlistBox.appendChild(playlistDiv); + } + } else { + if (queryType === 'simple' || queryType === 'playlist') { + playlistBox.innerHTML = '

No playlists found.

'; } else { - if (queryType === "simple" || queryType == "full") { - fullTextBox.innerHTML = "

No fulltext items found.

"; - } else { - fullTextBox.parentElement.style.display = "none"; - } + playlistBox.parentElement.style.display = 'none'; } + } + // fulltext + let allFullText = allResults.fulltext_results; + let fullTextBox = document.getElementById('fulltext-results'); + fullTextBox.innerHTML = ''; + fullTextBox.parentElement.style.display = 'block'; + if (allFullText.length > 0) { + for (let i = 0; i < allFullText.length; i++) { + const fullText = allFullText[i]; + if ('highlight' in fullText) { + const fullTextDiv = createFulltext(fullText); + fullTextBox.appendChild(fullTextDiv); + } + } + } else { + if (queryType === 'simple' || queryType === 'full') { + fullTextBox.innerHTML = '

No fulltext items found.

'; + } else { + fullTextBox.parentElement.style.display = 'none'; + } + } } - function createVideo(video, viewStyle) { - // create video item div from template - const videoId = video.youtube_id; - const mediaUrl = video.media_url; - const thumbUrl = "/cache/" + video.vid_thumb_url; - const videoTitle = video.title; - const videoPublished = video.published; - const videoDuration = video.player.duration_str; - if (video.player.watched) { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "watched"); - } else { - var watchStatusIndicator = createWatchStatusIndicator(videoId, "unwatched"); - }; - const channelId = video.channel.channel_id; - const channelName = video.channel.channel_name; - // build markup - const markup = ` + // create video item div from template + const videoId = video.youtube_id; + // const mediaUrl = video.media_url; + const thumbUrl = '/cache/' + video.vid_thumb_url; + const videoTitle = video.title; + const videoPublished = video.published; + const videoDuration = video.player.duration_str; + let watchStatusIndicator; + if (video.player.watched) { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'watched'); + } else { + watchStatusIndicator = createWatchStatusIndicator(videoId, 'unwatched'); + } + const channelId = video.channel.channel_id; + const channelName = video.channel.channel_name; + // build markup + const markup = `
@@ -943,26 +967,26 @@ function createVideo(video, viewStyle) {
`; - const videoDiv = document.createElement("div"); - videoDiv.setAttribute("class", "video-item " + viewStyle); - videoDiv.innerHTML = markup; - return videoDiv; + const videoDiv = document.createElement('div'); + videoDiv.setAttribute('class', 'video-item ' + viewStyle); + videoDiv.innerHTML = markup; + return videoDiv; } - function createChannel(channel, viewStyle) { - // create channel item div from template - const channelId = channel.channel_id; - const channelName = channel.channel_name; - const channelSubs = channel.channel_subs; - const channelLastRefresh = channel.channel_last_refresh; - if (channel.channel_subscribed) { - var button = ``; - } else { - var button = ``; - } - // build markup - const markup = ` + // create channel item div from template + const channelId = channel.channel_id; + const channelName = channel.channel_name; + const channelSubs = channel.channel_subs; + const channelLastRefresh = channel.channel_last_refresh; + let button; + if (channel.channel_subscribed) { + button = ``; + } else { + button = ``; + } + // build markup + const markup = `
${channelId}-banner @@ -988,25 +1012,26 @@ function createChannel(channel, viewStyle) {
`; - const channelDiv = document.createElement("div"); - channelDiv.setAttribute("class", "channel-item " + viewStyle); - channelDiv.innerHTML = markup; - return channelDiv; + const channelDiv = document.createElement('div'); + channelDiv.setAttribute('class', 'channel-item ' + viewStyle); + channelDiv.innerHTML = markup; + return channelDiv; } function createPlaylist(playlist, viewStyle) { - // create playlist item div from template - const playlistId = playlist.playlist_id; - const playlistName = playlist.playlist_name; - const playlistChannelId = playlist.playlist_channel_id; - const playlistChannel = playlist.playlist_channel; - const playlistLastRefresh = playlist.playlist_last_refresh; - if (playlist.playlist_subscribed) { - var button = ``; - } else { - var button = ``; - } - const markup = ` + // create playlist item div from template + const playlistId = playlist.playlist_id; + const playlistName = playlist.playlist_name; + const playlistChannelId = playlist.playlist_channel_id; + const playlistChannel = playlist.playlist_channel; + const playlistLastRefresh = playlist.playlist_last_refresh; + let button; + if (playlist.playlist_subscribed) { + button = ``; + } else { + button = ``; + } + const markup = `
${playlistId}-thumbnail @@ -1019,22 +1044,22 @@ function createPlaylist(playlist, viewStyle) { ${button}
`; - const playlistDiv = document.createElement("div"); - playlistDiv.setAttribute("class", "playlist-item " + viewStyle); - playlistDiv.innerHTML = markup; - return playlistDiv; + const playlistDiv = document.createElement('div'); + playlistDiv.setAttribute('class', 'playlist-item ' + viewStyle); + playlistDiv.innerHTML = markup; + return playlistDiv; } function createFulltext(fullText) { - const videoId = fullText.source.youtube_id; - const videoTitle = fullText.source.title; - const thumbUrl = fullText.source.vid_thumb_url; - const channelId = fullText.source.subtitle_channel_id; - const channelName = fullText.source.subtitle_channel; - const subtitleLine = fullText.highlight.subtitle_line[0]; - const subtitle_start = fullText.source.subtitle_start.split(".")[0]; - const subtitle_end = fullText.source.subtitle_end.split(".")[0]; - const markup = ` + const videoId = fullText.source.youtube_id; + const videoTitle = fullText.source.title; + const thumbUrl = fullText.source.vid_thumb_url; + const channelId = fullText.source.subtitle_channel_id; + const channelName = fullText.source.subtitle_channel; + const subtitleLine = fullText.highlight.subtitle_line[0]; + const subtitle_start = fullText.source.subtitle_start.split('.')[0]; + const subtitle_end = fullText.source.subtitle_end.split('.')[0]; + const markup = `
- ` - const fullTextDiv = document.createElement("div"); - fullTextDiv.setAttribute("class", "video-item list"); - fullTextDiv.innerHTML = markup; - return fullTextDiv + `; + const fullTextDiv = document.createElement('div'); + fullTextDiv.setAttribute('class', 'video-item list'); + fullTextDiv.innerHTML = markup; + return fullTextDiv; } // generic function sendPost(payload) { - var http = new XMLHttpRequest(); - http.open("POST", "/process/", true); - http.setRequestHeader("X-CSRFToken", getCookie("csrftoken")); - http.setRequestHeader("Content-type", "application/json"); - http.send(payload); + let http = new XMLHttpRequest(); + http.open('POST', '/process/', true); + http.setRequestHeader('X-CSRFToken', getCookie('csrftoken')); + http.setRequestHeader('Content-type', 'application/json'); + http.send(payload); } - function getCookie(c_name) { - if (document.cookie.length > 0) { - c_start = document.cookie.indexOf(c_name + "="); - if (c_start != -1) { - c_start = c_start + c_name.length + 1; - c_end = document.cookie.indexOf(";", c_start); - if (c_end == -1) c_end = document.cookie.length; - return unescape(document.cookie.substring(c_start,c_end)); - } + if (document.cookie.length > 0) { + let c_start = document.cookie.indexOf(c_name + '='); + if (c_start !== -1) { + c_start = c_start + c_name.length + 1; + let c_end = document.cookie.indexOf(';', c_start); + if (c_end === -1) c_end = document.cookie.length; + return unescape(document.cookie.substring(c_start, c_end)); } - return ""; + } + return ''; } - // animations function textReveal() { - var textBox = document.getElementById("text-reveal"); - var button = document.getElementById("text-reveal-button"); - var textBoxHeight = textBox.style.height; - if (textBoxHeight === "unset") { - textBox.style.height = "0px"; - button.innerText = "Show"; - } else { - textBox.style.height = "unset"; - button.innerText = "Hide"; - } + let textBox = document.getElementById('text-reveal'); + let button = document.getElementById('text-reveal-button'); + let textBoxHeight = textBox.style.height; + if (textBoxHeight === 'unset') { + textBox.style.height = '0px'; + button.innerText = 'Show'; + } else { + textBox.style.height = 'unset'; + button.innerText = 'Hide'; + } } function textExpand() { - var textBox = document.getElementById("text-expand"); - var button = document.getElementById("text-expand-button"); - var style = window.getComputedStyle(textBox) - if (style.webkitLineClamp === "none") { - textBox.style["-webkit-line-clamp"] = "4"; - button.innerText = "Show more"; - } else { - textBox.style["-webkit-line-clamp"] = "unset"; - button.innerText = "Show less"; - } + let textBox = document.getElementById('text-expand'); + let button = document.getElementById('text-expand-button'); + let style = window.getComputedStyle(textBox); + if (style.webkitLineClamp === 'none') { + textBox.style['-webkit-line-clamp'] = '4'; + button.innerText = 'Show more'; + } else { + textBox.style['-webkit-line-clamp'] = 'unset'; + button.innerText = 'Show less'; + } } // hide "show more" button if all text is already visible function textExpandButtonVisibilityUpdate() { - var textBox = document.getElementById("text-expand"); - var button = document.getElementById("text-expand-button"); - if (!textBox || !button) - return; + let textBox = document.getElementById('text-expand'); + let button = document.getElementById('text-expand-button'); + if (!textBox || !button) return; - var styles = window.getComputedStyle(textBox); - var textBoxLineClamp = styles.webkitLineClamp; - if (textBoxLineClamp === "unset") - return; // text box is in revealed state + let styles = window.getComputedStyle(textBox); + let textBoxLineClamp = styles.webkitLineClamp; + if (textBoxLineClamp === 'unset') return; // text box is in revealed state - if (textBox.offsetHeight < textBox.scrollHeight - || textBox.offsetWidth < textBox.scrollWidth) { - // the element has an overflow, show read more button - button.style.display = "inline-block"; - } else { - // the element doesn't have overflow - button.style.display = "none"; - } + if (textBox.offsetHeight < textBox.scrollHeight || textBox.offsetWidth < textBox.scrollWidth) { + // the element has an overflow, show read more button + button.style.display = 'inline-block'; + } else { + // the element doesn't have overflow + button.style.display = 'none'; + } } -document.addEventListener("readystatechange", textExpandButtonVisibilityUpdate); -window.addEventListener("resize", textExpandButtonVisibilityUpdate); +document.addEventListener('readystatechange', textExpandButtonVisibilityUpdate); +window.addEventListener('resize', textExpandButtonVisibilityUpdate); function showForm() { - var formElement = document.getElementById('hidden-form'); - var displayStyle = formElement.style.display; - if (displayStyle === "") { - formElement.style.display = 'block'; - } else { - formElement.style.display = ""; - } - animate('animate-icon', 'pulse-img'); + let formElement = document.getElementById('hidden-form'); + let displayStyle = formElement.style.display; + if (displayStyle === '') { + formElement.style.display = 'block'; + } else { + formElement.style.display = ''; + } + animate('animate-icon', 'pulse-img'); } function channelFilterDownload(value) { - if (value === "all") { - window.location = "/downloads/"; - } else { - window.location.search = "?channel=" + value; - } + if (value === 'all') { + window.location = '/downloads/'; + } else { + window.location.search = '?channel=' + value; + } } function showOverwrite() { - var overwriteDiv = document.getElementById("overwrite-form"); - if (overwriteDiv.classList.contains("hidden-overwrite")) { - overwriteDiv.classList.remove("hidden-overwrite"); - } else { - overwriteDiv.classList.add("hidden-overwrite") - } + let overwriteDiv = document.getElementById('overwrite-form'); + if (overwriteDiv.classList.contains('hidden-overwrite')) { + overwriteDiv.classList.remove('hidden-overwrite'); + } else { + overwriteDiv.classList.add('hidden-overwrite'); + } } function animate(elementId, animationClass) { - var toAnimate = document.getElementById(elementId); - if (toAnimate.className !== animationClass) { - toAnimate.className = animationClass; - } else { - toAnimate.classList.remove(animationClass); - } + let toAnimate = document.getElementById(elementId); + if (toAnimate.className !== animationClass) { + toAnimate.className = animationClass; + } else { + toAnimate.classList.remove(animationClass); + } } // keep track of changes to the subtitles list made with the native UI @@ -1181,16 +1201,16 @@ addEventListener('DOMContentLoaded', recordTextTrackChanges); let lastSeenTextTrack = 0; function recordTextTrackChanges() { - let player = getVideoPlayer(); - if (player == null) { - return; + let player = getVideoPlayer(); + if (player == null) { + return; + } + player.textTracks.addEventListener('change', () => { + let active = [...player.textTracks].findIndex(x => x.mode === 'showing'); + if (active !== -1) { + lastSeenTextTrack = active; } - player.textTracks.addEventListener('change', () => { - let active = [...player.textTracks].findIndex(x => x.mode === 'showing'); - if (active !== -1) { - lastSeenTextTrack = active; - } - }); + }); } // keyboard shortcuts for the video player @@ -1198,98 +1218,108 @@ document.addEventListener('keydown', doShortcut); let modalHideTimeout = -1; function showModal(html, duration) { - let player = getVideoPlayer(); - let modal = document.querySelector('.video-modal-text'); - modal.innerHTML = html; - modal.style.display = 'initial'; - clearTimeout(modalHideTimeout); - modalHideTimeout = setTimeout(() => { modal.style.display = 'none'; }, duration); + let modal = document.querySelector('.video-modal-text'); + modal.innerHTML = html; + modal.style.display = 'initial'; + clearTimeout(modalHideTimeout); + modalHideTimeout = setTimeout(() => { + modal.style.display = 'none'; + }, duration); } -let videoSpeeds = [.25, .5, .75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3]; +let videoSpeeds = [0.25, 0.5, 0.75, 1, 1.25, 1.5, 1.75, 2, 2.25, 2.5, 2.75, 3]; function doShortcut(e) { - if (!(e.target instanceof HTMLElement)) { - return; + if (!(e.target instanceof HTMLElement)) { + return; + } + let target = e.target; + let targetName = target.nodeName.toLowerCase(); + if ( + targetName === 'textarea' || + targetName === 'input' || + targetName === 'select' || + target.isContentEditable + ) { + return; + } + if (e.altKey || e.ctrlKey || e.metaKey) { + return; + } + let player = getVideoPlayer(); + if (player == null) { + // not on the video page + return; + } + switch (e.key) { + case 'c': { + // toggle captions + let tracks = [...player.textTracks]; + if (tracks.length === 0) { + break; + } + let active = tracks.find(x => x.mode === 'showing'); + if (active != null) { + active.mode = 'disabled'; + } else { + tracks[lastSeenTextTrack].mode = 'showing'; + } + break; } - let target = e.target; - let targetName = target.nodeName.toLowerCase(); - if (targetName === 'textarea' || targetName === 'input' || targetName === 'select' || target.isContentEditable) { - return; + case 'm': { + player.muted = !player.muted; + break; } - if (e.altKey || e.ctrlKey || e.metaKey) { - return; + case 'ArrowLeft': { + if (targetName === 'video') { + // hitting arrows while the video is focused will use the built-in skip + break; + } + showModal('- 5 seconds', 500); + player.currentTime -= 5; + break; } - let player = getVideoPlayer(); - if (player == null) { - // not on the video page - return; + case 'ArrowRight': { + if (targetName === 'video') { + // hitting space while the video is focused will use the built-in skip + break; + } + showModal('+ 5 seconds', 500); + player.currentTime += 5; + break; } - switch (e.key) { - case 'c': { - // toggle captions - let tracks = [...player.textTracks]; - if (tracks.length === 0) { - break; - } - let active = tracks.find(x => x.mode === 'showing'); - if (active != null) { - active.mode = 'disabled'; - } else { - tracks[lastSeenTextTrack].mode = 'showing'; - } - break; - } - case 'm': { - player.muted = !player.muted; - break; - } - case 'ArrowLeft': { - if (targetName === 'video') { - // hitting arrows while the video is focused will use the built-in skip - break; - } - showModal('- 5 seconds', 500); - player.currentTime -= 5; - break; - } - case 'ArrowRight': { - if (targetName === 'video') { - // hitting space while the video is focused will use the built-in skip - break; - } - showModal('+ 5 seconds', 500); - player.currentTime += 5; - break; - } - case '<': - case '>': { - // change speed - let currentSpeedIdx = videoSpeeds.findIndex(s => s >= player.playbackRate); - if (currentSpeedIdx === -1) { - // handle the case where the user manually set the speed above our max speed - currentSpeedIdx = videoSpeeds.length - 1; - } - let newSpeedIdx = e.key === '<' ? Math.max(0, currentSpeedIdx - 1) : Math.min(videoSpeeds.length - 1, currentSpeedIdx + 1); - let newSpeed = videoSpeeds[newSpeedIdx]; - player.playbackRate = newSpeed; - showModal(newSpeed + 'x', 500); - break; - } - case ' ': { - if (targetName === 'video') { - // hitting space while the video is focused will toggle it anyway - break; - } - e.preventDefault(); - if (player.paused) { - player.play(); - } else { - player.pause(); - } - break; - } - case '?': { - showModal(` + case '<': + case '>': { + // change speed + let currentSpeedIdx = videoSpeeds.findIndex(s => s >= player.playbackRate); + if (currentSpeedIdx === -1) { + // handle the case where the user manually set the speed above our max speed + currentSpeedIdx = videoSpeeds.length - 1; + } + let newSpeedIdx = + e.key === '<' + ? Math.max(0, currentSpeedIdx - 1) + : Math.min(videoSpeeds.length - 1, currentSpeedIdx + 1); + let newSpeed = videoSpeeds[newSpeedIdx]; + player.playbackRate = newSpeed; + showModal(newSpeed + 'x', 500); + break; + } + case ' ': { + if (targetName === 'video') { + // hitting space while the video is focused will toggle it anyway + break; + } + e.preventDefault(); + if (player.paused) { + player.play(); + } else { + player.pause(); + } + break; + } + case '?': { + showModal( + ` @@ -1298,9 +1328,10 @@ function doShortcut(e) { - `, 3000); - break; - } + `, + 3000 + ); + break; } + } } -
Show help?
Toggle mutem
Decrease speed<
Back 5 seconds
Forward 5 seconds