From 44802bcb45e8396719fde80b6c3452c32d45e1d0 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Thu, 3 Jun 2021 20:52:06 +0200 Subject: [PATCH 01/24] bling --- appinfo.json | 5 ++-- icon.png | Bin 8572 -> 838 bytes icon.svg | 77 ++++++++++++++++++++++++++++++++++++++++++++++++++ largeIcon.png | Bin 14326 -> 1263 bytes 4 files changed, 80 insertions(+), 2 deletions(-) create mode 100644 icon.svg diff --git a/appinfo.json b/appinfo.json index d63573b3..aaab3af1 100644 --- a/appinfo.json +++ b/appinfo.json @@ -4,9 +4,10 @@ "vendor": "My Company", "type": "web", "main": "index.html", - "title": "Youtube Without ADs", + "title": "YouTube AdFree", "icon": "icon.png", "largeIcon": "largeIcon.png", + "iconColor": "#ff0000", "support360Content": true, "accessibility": { "supportsAudioGuidance": true @@ -20,4 +21,4 @@ "dialAppName": "YouTube", "disableBackHistoryAPI": true, "noSplashOnLaunch": true -} \ No newline at end of file +} diff --git a/icon.png b/icon.png index c1742ede0e2d5bcff3b46d234e58806b588c2fc0..21444b0f87735489f848441c7d14e6dbbefa99ce 100755 GIT binary patch literal 838 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 + + + + + + + + + + image/svg+xml + + + + + + + + + + diff --git a/largeIcon.png b/largeIcon.png index 1e8d86e5bb812400ef048826ecd6358fd8351bb3..6e9ff1653ae885417455884c415d1085e62da130 100755 GIT binary patch literal 1263 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 From 5e6a14d7232faec53fedf1fb567ce9f3d2fc7b2a Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Fri, 4 Jun 2021 22:40:34 +0200 Subject: [PATCH 02/24] WIP: webpack build infrastructure --- .gitignore | 1 + appinfo.json => assets/appinfo.json | 0 icon.png => assets/icon.png | Bin icon.svg => assets/icon.svg | 0 largeIcon.png => assets/largeIcon.png | Bin babel.config.json | 16 + package-lock.json | 3055 +++++++++++++++++++++-- package.json | 11 +- index.html => src/index.html | 2 +- index.js => src/index.js | 0 {webOSUserScripts => src}/userScript.js | 0 webpack.config.js | 42 + 12 files changed, 2983 insertions(+), 144 deletions(-) rename appinfo.json => assets/appinfo.json (100%) rename icon.png => assets/icon.png (100%) rename icon.svg => assets/icon.svg (100%) rename largeIcon.png => assets/largeIcon.png (100%) create mode 100644 babel.config.json rename index.html => src/index.html (90%) rename index.js => src/index.js (100%) rename {webOSUserScripts => src}/userScript.js (100%) create mode 100644 webpack.config.js diff --git a/.gitignore b/.gitignore index 6a2d7faf..6602020e 100644 --- a/.gitignore +++ b/.gitignore @@ -1,2 +1,3 @@ *.ipk node_modules +dist diff --git a/appinfo.json b/assets/appinfo.json similarity index 100% rename from appinfo.json rename to assets/appinfo.json diff --git a/icon.png b/assets/icon.png similarity index 100% rename from icon.png rename to assets/icon.png diff --git a/icon.svg b/assets/icon.svg similarity index 100% rename from icon.svg rename to assets/icon.svg diff --git a/largeIcon.png b/assets/largeIcon.png similarity index 100% rename from largeIcon.png rename to assets/largeIcon.png diff --git a/babel.config.json b/babel.config.json new file mode 100644 index 00000000..114908b9 --- /dev/null +++ b/babel.config.json @@ -0,0 +1,16 @@ +{ + "plugins": [ + ], + "presets": [ + [ + "@babel/env", + { + "targets": { + "chrome": "38" + }, + "useBuiltIns": "entry", + "corejs": "3.0" + } + ] + ] +} diff --git a/package-lock.json b/package-lock.json index 47bf7220..578f7f46 100644 --- a/package-lock.json +++ b/package-lock.json @@ -4,6 +4,1279 @@ "lockfileVersion": 1, "requires": true, "dependencies": { + "@babel/code-frame": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.13.tgz", + "integrity": "sha512-HV1Cm0Q3ZrpCR93tkWOYiuYIgLxZXZFVG2VgK+MBWjUqZTundupbfx2aXarXuw5Ko5aMcjtJgbSs4vUGBS5v6g==", + "dev": true, + "requires": { + "@babel/highlight": "^7.12.13" + } + }, + "@babel/compat-data": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.14.4.tgz", + "integrity": "sha512-i2wXrWQNkH6JplJQGn3Rd2I4Pij8GdHkXwHMxm+zV5YG/Jci+bCNrWZEWC4o+umiDkRrRs4dVzH3X4GP7vyjQQ==", + "dev": true + }, + "@babel/core": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.14.3.tgz", + "integrity": "sha512-jB5AmTKOCSJIZ72sd78ECEhuPiDMKlQdDI/4QRI6lzYATx5SSogS1oQA2AoPecRCknm30gHi2l+QVvNUu3wZAg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.3", + "@babel/helper-compilation-targets": "^7.13.16", + "@babel/helper-module-transforms": "^7.14.2", + "@babel/helpers": "^7.14.0", + "@babel/parser": "^7.14.3", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2", + "convert-source-map": "^1.7.0", + "debug": "^4.1.0", + "gensync": "^1.0.0-beta.2", + "json5": "^2.1.2", + "semver": "^6.3.0", + "source-map": "^0.5.0" + } + }, + "@babel/generator": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.14.3.tgz", + "integrity": "sha512-bn0S6flG/j0xtQdz3hsjJ624h3W0r3llttBMfyHX3YrZ/KtLYr15bjA0FXkgW7FpvrDuTuElXeVjiKlYRpnOFA==", + "dev": true, + "requires": { + "@babel/types": "^7.14.2", + "jsesc": "^2.5.1", + "source-map": "^0.5.0" + } + }, + "@babel/helper-annotate-as-pure": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-annotate-as-pure/-/helper-annotate-as-pure-7.12.13.tgz", + "integrity": "sha512-7YXfX5wQ5aYM/BOlbSccHDbuXXFPxeoUmfWtz8le2yTkTZc+BxsiEnENFoi2SlmA8ewDkG2LgIMIVzzn2h8kfw==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-builder-binary-assignment-operator-visitor": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-builder-binary-assignment-operator-visitor/-/helper-builder-binary-assignment-operator-visitor-7.12.13.tgz", + "integrity": "sha512-CZOv9tGphhDRlVjVkAgm8Nhklm9RzSmWpX2my+t7Ua/KT616pEzXsQCjinzvkRvHWJ9itO4f296efroX23XCMA==", + "dev": true, + "requires": { + "@babel/helper-explode-assignable-expression": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-compilation-targets": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.14.4.tgz", + "integrity": "sha512-JgdzOYZ/qGaKTVkn5qEDV/SXAh8KcyUVkCoSWGN8T3bwrgd6m+/dJa2kVGi6RJYJgEYPBdZ84BZp9dUjNWkBaA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.4", + "@babel/helper-validator-option": "^7.12.17", + "browserslist": "^4.16.6", + "semver": "^6.3.0" + } + }, + "@babel/helper-create-class-features-plugin": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/helper-create-class-features-plugin/-/helper-create-class-features-plugin-7.14.4.tgz", + "integrity": "sha512-idr3pthFlDCpV+p/rMgGLGYIVtazeatrSOQk8YzO2pAepIjQhCN3myeihVg58ax2bbbGK9PUE1reFi7axOYIOw==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-replace-supers": "^7.14.4", + "@babel/helper-split-export-declaration": "^7.12.13" + } + }, + "@babel/helper-create-regexp-features-plugin": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/helper-create-regexp-features-plugin/-/helper-create-regexp-features-plugin-7.14.3.tgz", + "integrity": "sha512-JIB2+XJrb7v3zceV2XzDhGIB902CmKGSpSl4q2C6agU9SNLG/2V1RtFRGPG1Ajh9STj3+q6zJMOC+N/pp2P9DA==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "regexpu-core": "^4.7.1" + } + }, + "@babel/helper-define-polyfill-provider": { + "version": "0.2.3", + "resolved": "https://registry.npmjs.org/@babel/helper-define-polyfill-provider/-/helper-define-polyfill-provider-0.2.3.tgz", + "integrity": "sha512-RH3QDAfRMzj7+0Nqu5oqgO5q9mFtQEVvCRsi8qCEfzLR9p2BHfn5FzhSB2oj1fF7I2+DcTORkYaQ6aTR9Cofew==", + "dev": true, + "requires": { + "@babel/helper-compilation-targets": "^7.13.0", + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/traverse": "^7.13.0", + "debug": "^4.1.1", + "lodash.debounce": "^4.0.8", + "resolve": "^1.14.2", + "semver": "^6.1.2" + } + }, + "@babel/helper-explode-assignable-expression": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-explode-assignable-expression/-/helper-explode-assignable-expression-7.13.0.tgz", + "integrity": "sha512-qS0peLTDP8kOisG1blKbaoBg/o9OSa1qoumMjTK5pM+KDTtpxpsiubnCGP34vK8BXGcb2M9eigwgvoJryrzwWA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.0" + } + }, + "@babel/helper-function-name": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.14.2.tgz", + "integrity": "sha512-NYZlkZRydxw+YT56IlhIcS8PAhb+FEUiOzuhFTfqDyPmzAhRge6ua0dQYT/Uh0t/EDHq05/i+e5M2d4XvjgarQ==", + "dev": true, + "requires": { + "@babel/helper-get-function-arity": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-get-function-arity": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-get-function-arity/-/helper-get-function-arity-7.12.13.tgz", + "integrity": "sha512-DjEVzQNz5LICkzN0REdpD5prGoidvbdYk1BVgRUOINaWJP2t6avB27X1guXK1kXNrX0WMfsrm1A/ZBthYuIMQg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-hoist-variables": { + "version": "7.13.16", + "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.13.16.tgz", + "integrity": "sha512-1eMtTrXtrwscjcAeO4BVK+vvkxaLJSPFz1w1KLawz6HLNi9bPFGBNwwDyVfiu1Tv/vRRFYfoGaKhmAQPGPn5Wg==", + "dev": true, + "requires": { + "@babel/traverse": "^7.13.15", + "@babel/types": "^7.13.16" + } + }, + "@babel/helper-member-expression-to-functions": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-member-expression-to-functions/-/helper-member-expression-to-functions-7.13.12.tgz", + "integrity": "sha512-48ql1CLL59aKbU94Y88Xgb2VFy7a95ykGRbJJaaVv+LX5U8wFpLfiGXJJGUozsmA1oEh/o5Bp60Voq7ACyA/Sw==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-imports": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.13.12.tgz", + "integrity": "sha512-4cVvR2/1B693IuOvSI20xqqa/+bl7lqAMR59R4iu39R9aOX8/JoYY1sFaNvUMyMBGnHdwvJgUrzNLoUZxXypxA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-module-transforms": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.14.2.tgz", + "integrity": "sha512-OznJUda/soKXv0XhpvzGWDnml4Qnwp16GN+D/kZIdLsWoHj05kyu8Rm5kXmMef+rVJZ0+4pSGLkeixdqNUATDA==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.13.12", + "@babel/helper-replace-supers": "^7.13.12", + "@babel/helper-simple-access": "^7.13.12", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/helper-validator-identifier": "^7.14.0", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.2" + } + }, + "@babel/helper-optimise-call-expression": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-optimise-call-expression/-/helper-optimise-call-expression-7.12.13.tgz", + "integrity": "sha512-BdWQhoVJkp6nVjB7nkFWcn43dkprYauqtk++Py2eaf/GRDFm5BxRqEIZCiHlZUGAVmtwKcsVL1dC68WmzeFmiA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-plugin-utils": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-plugin-utils/-/helper-plugin-utils-7.13.0.tgz", + "integrity": "sha512-ZPafIPSwzUlAoWT8DKs1W2VyF2gOWthGd5NGFMsBcMMol+ZhK+EQY/e6V96poa6PA/Bh+C9plWN0hXO1uB8AfQ==", + "dev": true + }, + "@babel/helper-remap-async-to-generator": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-remap-async-to-generator/-/helper-remap-async-to-generator-7.13.0.tgz", + "integrity": "sha512-pUQpFBE9JvC9lrQbpX0TmeNIy5s7GnZjna2lhhcHC7DzgBs6fWn722Y5cfwgrtrqc7NAJwMvOa0mKhq6XaE4jg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-wrap-function": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "@babel/helper-replace-supers": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/helper-replace-supers/-/helper-replace-supers-7.14.4.tgz", + "integrity": "sha512-zZ7uHCWlxfEAAOVDYQpEf/uyi1dmeC7fX4nCf2iz9drnCwi1zvwXL3HwWWNXUQEJ1k23yVn3VbddiI9iJEXaTQ==", + "dev": true, + "requires": { + "@babel/helper-member-expression-to-functions": "^7.13.12", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/traverse": "^7.14.2", + "@babel/types": "^7.14.4" + } + }, + "@babel/helper-simple-access": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.13.12.tgz", + "integrity": "sha512-7FEjbrx5SL9cWvXioDbnlYTppcZGuCY6ow3/D5vMggb2Ywgu4dMrpTJX0JdQAIcRRUElOIxF3yEooa9gUb9ZbA==", + "dev": true, + "requires": { + "@babel/types": "^7.13.12" + } + }, + "@babel/helper-skip-transparent-expression-wrappers": { + "version": "7.12.1", + "resolved": "https://registry.npmjs.org/@babel/helper-skip-transparent-expression-wrappers/-/helper-skip-transparent-expression-wrappers-7.12.1.tgz", + "integrity": "sha512-Mf5AUuhG1/OCChOJ/HcADmvcHM42WJockombn8ATJG3OnyiSxBK/Mm5x78BQWvmtXZKHgbjdGL2kin/HOLlZGA==", + "dev": true, + "requires": { + "@babel/types": "^7.12.1" + } + }, + "@babel/helper-split-export-declaration": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.12.13.tgz", + "integrity": "sha512-tCJDltF83htUtXx5NLcaDqRmknv652ZWCHyoTETf1CXYJdPC7nohZohjUgieXhv0hTJdRf2FjDueFehdNucpzg==", + "dev": true, + "requires": { + "@babel/types": "^7.12.13" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.14.0.tgz", + "integrity": "sha512-V3ts7zMSu5lfiwWDVWzRDGIN+lnCEUdaXgtVHJgLb1rGaA6jMrtB9EmE7L18foXJIE8Un/A/h6NJfGQp/e1J4A==", + "dev": true + }, + "@babel/helper-validator-option": { + "version": "7.12.17", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.12.17.tgz", + "integrity": "sha512-TopkMDmLzq8ngChwRlyjR6raKD6gMSae4JdYDB8bByKreQgG0RBTuKe9LRxW3wFtUnjxOPRKBDwEH6Mg5KeDfw==", + "dev": true + }, + "@babel/helper-wrap-function": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/helper-wrap-function/-/helper-wrap-function-7.13.0.tgz", + "integrity": "sha512-1UX9F7K3BS42fI6qd2A4BjKzgGjToscyZTdp1DjknHLCIvpgne6918io+aL5LXFcER/8QWiwpoY902pVEqgTXA==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.12.13", + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.13.0", + "@babel/types": "^7.13.0" + } + }, + "@babel/helpers": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.14.0.tgz", + "integrity": "sha512-+ufuXprtQ1D1iZTO/K9+EBRn+qPWMJjZSw/S0KlFrxCw4tkrzv9grgpDHkY9MeQTjTY8i2sp7Jep8DfU6tN9Mg==", + "dev": true, + "requires": { + "@babel/template": "^7.12.13", + "@babel/traverse": "^7.14.0", + "@babel/types": "^7.14.0" + } + }, + "@babel/highlight": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.14.0.tgz", + "integrity": "sha512-YSCOwxvTYEIMSGaBQb5kDDsCopDdiUGsqpatp3fOlI4+2HQSkTmEVWnVuySdAC5EWCqSWWTv0ib63RjR7dTBdg==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + } + }, + "@babel/parser": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.14.4.tgz", + "integrity": "sha512-ArliyUsWDUqEGfWcmzpGUzNfLxTdTp6WU4IuP6QFSp9gGfWS6boxFCkJSJ/L4+RG8z/FnIU3WxCk6hPL9SSWeA==", + "dev": true + }, + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": { + "version": "7.13.12", + "resolved": "https://registry.npmjs.org/@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining/-/plugin-bugfix-v8-spread-parameters-in-optional-chaining-7.13.12.tgz", + "integrity": "sha512-d0u3zWKcoZf379fOeJdr1a5WPDny4aOFZ6hlfKivgK0LY7ZxNfoaHL2fWwdGtHyVvra38FC+HVYkO+byfSA8AQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-proposal-optional-chaining": "^7.13.12" + } + }, + "@babel/plugin-proposal-async-generator-functions": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-async-generator-functions/-/plugin-proposal-async-generator-functions-7.14.2.tgz", + "integrity": "sha512-b1AM4F6fwck4N8ItZ/AtC4FP/cqZqmKRQ4FaTDutwSYyjuhtvsGEMLK4N/ztV/ImP40BjIDyMgBQAeAMsQYVFQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0", + "@babel/plugin-syntax-async-generators": "^7.8.4" + } + }, + "@babel/plugin-proposal-class-properties": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-properties/-/plugin-proposal-class-properties-7.13.0.tgz", + "integrity": "sha512-KnTDjFNC1g+45ka0myZNvSBFLhNCLN+GeGYLDEA8Oq7MZ6yMgfLoIRh86GRT0FjtJhZw8JyUskP9uvj5pHM9Zg==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-proposal-class-static-block": { + "version": "7.14.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-class-static-block/-/plugin-proposal-class-static-block-7.14.3.tgz", + "integrity": "sha512-HEjzp5q+lWSjAgJtSluFDrGGosmwTgKwCXdDQZvhKsRlwv3YdkUEqxNrrjesJd+B9E9zvr1PVPVBvhYZ9msjvQ==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.14.3", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-class-static-block": "^7.12.13" + } + }, + "@babel/plugin-proposal-dynamic-import": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-dynamic-import/-/plugin-proposal-dynamic-import-7.14.2.tgz", + "integrity": "sha512-oxVQZIWFh91vuNEMKltqNsKLFWkOIyJc95k2Gv9lWVyDfPUQGSSlbDEgWuJUU1afGE9WwlzpucMZ3yDRHIItkA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-dynamic-import": "^7.8.3" + } + }, + "@babel/plugin-proposal-export-namespace-from": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-export-namespace-from/-/plugin-proposal-export-namespace-from-7.14.2.tgz", + "integrity": "sha512-sRxW3z3Zp3pFfLAgVEvzTFutTXax837oOatUIvSG9o5gRj9mKwm3br1Se5f4QalTQs9x4AzlA/HrCWbQIHASUQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3" + } + }, + "@babel/plugin-proposal-json-strings": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-json-strings/-/plugin-proposal-json-strings-7.14.2.tgz", + "integrity": "sha512-w2DtsfXBBJddJacXMBhElGEYqCZQqN99Se1qeYn8DVLB33owlrlLftIbMzn5nz1OITfDVknXF433tBrLEAOEjA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-json-strings": "^7.8.3" + } + }, + "@babel/plugin-proposal-logical-assignment-operators": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-logical-assignment-operators/-/plugin-proposal-logical-assignment-operators-7.14.2.tgz", + "integrity": "sha512-1JAZtUrqYyGsS7IDmFeaem+/LJqujfLZ2weLR9ugB0ufUPjzf8cguyVT1g5im7f7RXxuLq1xUxEzvm68uYRtGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4" + } + }, + "@babel/plugin-proposal-nullish-coalescing-operator": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-nullish-coalescing-operator/-/plugin-proposal-nullish-coalescing-operator-7.14.2.tgz", + "integrity": "sha512-ebR0zU9OvI2N4qiAC38KIAK75KItpIPTpAtd2r4OZmMFeKbKJpUFLYP2EuDut82+BmYi8sz42B+TfTptJ9iG5Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3" + } + }, + "@babel/plugin-proposal-numeric-separator": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-numeric-separator/-/plugin-proposal-numeric-separator-7.14.2.tgz", + "integrity": "sha512-DcTQY9syxu9BpU3Uo94fjCB3LN9/hgPS8oUL7KrSW3bA2ePrKZZPJcc5y0hoJAM9dft3pGfErtEUvxXQcfLxUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-numeric-separator": "^7.10.4" + } + }, + "@babel/plugin-proposal-object-rest-spread": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-object-rest-spread/-/plugin-proposal-object-rest-spread-7.14.4.tgz", + "integrity": "sha512-AYosOWBlyyXEagrPRfLJ1enStufsr7D1+ddpj8OLi9k7B6+NdZ0t/9V7Fh+wJ4g2Jol8z2JkgczYqtWrZd4vbA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.4", + "@babel/helper-compilation-targets": "^7.14.4", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-transform-parameters": "^7.14.2" + } + }, + "@babel/plugin-proposal-optional-catch-binding": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-catch-binding/-/plugin-proposal-optional-catch-binding-7.14.2.tgz", + "integrity": "sha512-XtkJsmJtBaUbOxZsNk0Fvrv8eiqgneug0A6aqLFZ4TSkar2L5dSXWcnUKHgmjJt49pyB/6ZHvkr3dPgl9MOWRQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3" + } + }, + "@babel/plugin-proposal-optional-chaining": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-optional-chaining/-/plugin-proposal-optional-chaining-7.14.2.tgz", + "integrity": "sha512-qQByMRPwMZJainfig10BoaDldx/+VDtNcrA7qdNaEOAj6VXud+gfrkA8j4CRAU5HjnWREXqIpSpH30qZX1xivA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1", + "@babel/plugin-syntax-optional-chaining": "^7.8.3" + } + }, + "@babel/plugin-proposal-private-methods": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-methods/-/plugin-proposal-private-methods-7.13.0.tgz", + "integrity": "sha512-MXyyKQd9inhx1kDYPkFRVOBXQ20ES8Pto3T7UZ92xj2mY0EVD8oAVzeyYuVfy/mxAdTSIayOvg+aVzcHV2bn6Q==", + "dev": true, + "requires": { + "@babel/helper-create-class-features-plugin": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-proposal-private-property-in-object": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-private-property-in-object/-/plugin-proposal-private-property-in-object-7.14.0.tgz", + "integrity": "sha512-59ANdmEwwRUkLjB7CRtwJxxwtjESw+X2IePItA+RGQh+oy5RmpCh/EvVVvh5XQc3yxsm5gtv0+i9oBZhaDNVTg==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-create-class-features-plugin": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/plugin-syntax-private-property-in-object": "^7.14.0" + } + }, + "@babel/plugin-proposal-unicode-property-regex": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-proposal-unicode-property-regex/-/plugin-proposal-unicode-property-regex-7.12.13.tgz", + "integrity": "sha512-XyJmZidNfofEkqFV5VC/bLabGmO5QzenPO/YOfGuEbgU+2sSwMmio3YLb4WtBgcmmdwZHyVyv8on77IUjQ5Gvg==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-async-generators": { + "version": "7.8.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-async-generators/-/plugin-syntax-async-generators-7.8.4.tgz", + "integrity": "sha512-tycmZxkGfZaxhMRbXlPXuVFpdWlXpir2W4AMhSJgRKzk/eDlIXOhb2LHWoLpDF7TEHylV5zNhykX6KAgHJmTNw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-class-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-properties/-/plugin-syntax-class-properties-7.12.13.tgz", + "integrity": "sha512-fm4idjKla0YahUNgFNLCB0qySdsoPiZP3iQE3rky0mBUtMZ23yDJ9SJdg6dXTSDnulOVqiF3Hgr9nbXvXTQZYA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-class-static-block": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-class-static-block/-/plugin-syntax-class-static-block-7.12.13.tgz", + "integrity": "sha512-ZmKQ0ZXR0nYpHZIIuj9zE7oIqCx2hw9TKi+lIo73NNrMPAZGHfS92/VRV0ZmPj6H2ffBgyFHXvJ5NYsNeEaP2A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-syntax-dynamic-import": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-dynamic-import/-/plugin-syntax-dynamic-import-7.8.3.tgz", + "integrity": "sha512-5gdGbFon+PszYzqs83S3E5mpi7/y/8M9eC90MRTZfduQOYW76ig6SOSPNe41IG5LoP3FGBn2N0RjVDSQiS94kQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-export-namespace-from": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-export-namespace-from/-/plugin-syntax-export-namespace-from-7.8.3.tgz", + "integrity": "sha512-MXf5laXo6c1IbEbegDmzGPwGNTsHZmEy6QGznu5Sh2UCWvueywb2ee+CCE4zQiZstxU9BMoQO9i6zUFSY0Kj0Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.3" + } + }, + "@babel/plugin-syntax-json-strings": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-json-strings/-/plugin-syntax-json-strings-7.8.3.tgz", + "integrity": "sha512-lY6kdGpWHvjoe2vk4WrAapEuBR69EMxZl+RoGRhrFGNYVK8mOPAW8VfbT/ZgrFbXlDNiiaxQnAtgVCZ6jv30EA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-logical-assignment-operators": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-logical-assignment-operators/-/plugin-syntax-logical-assignment-operators-7.10.4.tgz", + "integrity": "sha512-d8waShlpFDinQ5MtvGU9xDAOzKH47+FFoney2baFIoMr952hKOLp1HR7VszoZvOsV/4+RRszNY7D17ba0te0ig==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-nullish-coalescing-operator": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-nullish-coalescing-operator/-/plugin-syntax-nullish-coalescing-operator-7.8.3.tgz", + "integrity": "sha512-aSff4zPII1u2QD7y+F8oDsz19ew4IGEJg9SVW+bqwpwtfFleiQDMdzA/R+UlWDzfnHFCxxleFT0PMIrR36XLNQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-numeric-separator": { + "version": "7.10.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-numeric-separator/-/plugin-syntax-numeric-separator-7.10.4.tgz", + "integrity": "sha512-9H6YdfkcK/uOnY/K7/aA2xpzaAgkQn37yzWUMRK7OaPOqOpGS1+n0H5hxT9AUw9EsSjPW8SVyMJwYRtWs3X3ug==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.10.4" + } + }, + "@babel/plugin-syntax-object-rest-spread": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-object-rest-spread/-/plugin-syntax-object-rest-spread-7.8.3.tgz", + "integrity": "sha512-XoqMijGZb9y3y2XskN+P1wUGiVwWZ5JmoDRwx5+3GmEplNyVM2s2Dg8ILFQm8rWM48orGy5YpI5Bl8U1y7ydlA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-catch-binding": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-catch-binding/-/plugin-syntax-optional-catch-binding-7.8.3.tgz", + "integrity": "sha512-6VPD0Pc1lpTqw0aKoeRTMiB+kWhAoT24PA+ksWSBrFtl5SIRVpZlwN3NNPQjehA2E/91FV3RjLWoVTglWcSV3Q==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-optional-chaining": { + "version": "7.8.3", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-optional-chaining/-/plugin-syntax-optional-chaining-7.8.3.tgz", + "integrity": "sha512-KoK9ErH1MBlCPxV0VANkXW2/dw4vlbGDrFgz8bmUsBGYkFRcbRwMh6cIJubdPrkxRwuGdtCk0v/wPTKbQgBjkg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.8.0" + } + }, + "@babel/plugin-syntax-private-property-in-object": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-private-property-in-object/-/plugin-syntax-private-property-in-object-7.14.0.tgz", + "integrity": "sha512-bda3xF8wGl5/5btF794utNOL0Jw+9jE5C1sLZcoK7c4uonE/y3iQiyG+KbkF3WBV/paX58VCpjhxLPkdj5Fe4w==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-syntax-top-level-await": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-syntax-top-level-await/-/plugin-syntax-top-level-await-7.12.13.tgz", + "integrity": "sha512-A81F9pDwyS7yM//KwbCSDqy3Uj4NMIurtplxphWxoYtNPov7cJsDkAFNNyVlIZ3jwGycVsurZ+LtOA8gZ376iQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-arrow-functions": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-arrow-functions/-/plugin-transform-arrow-functions-7.13.0.tgz", + "integrity": "sha512-96lgJagobeVmazXFaDrbmCLQxBysKu7U6Do3mLsx27gf5Dk85ezysrs2BZUpXD703U/Su1xTBDxxar2oa4jAGg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-async-to-generator": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-async-to-generator/-/plugin-transform-async-to-generator-7.13.0.tgz", + "integrity": "sha512-3j6E004Dx0K3eGmhxVJxwwI89CTJrce7lg3UrtFuDAVQ/2+SJ/h/aSFOeE6/n0WB1GsOffsJp6MnPQNQ8nmwhg==", + "dev": true, + "requires": { + "@babel/helper-module-imports": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-remap-async-to-generator": "^7.13.0" + } + }, + "@babel/plugin-transform-block-scoped-functions": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoped-functions/-/plugin-transform-block-scoped-functions-7.12.13.tgz", + "integrity": "sha512-zNyFqbc3kI/fVpqwfqkg6RvBgFpC4J18aKKMmv7KdQ/1GgREapSJAykLMVNwfRGO3BtHj3YQZl8kxCXPcVMVeg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-block-scoping": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-block-scoping/-/plugin-transform-block-scoping-7.14.4.tgz", + "integrity": "sha512-5KdpkGxsZlTk+fPleDtGKsA+pon28+ptYmMO8GBSa5fHERCJWAzj50uAfCKBqq42HO+Zot6JF1x37CRprwmN4g==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-classes": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-classes/-/plugin-transform-classes-7.14.4.tgz", + "integrity": "sha512-p73t31SIj6y94RDVX57rafVjttNr8MvKEgs5YFatNB/xC68zM3pyosuOEcQmYsYlyQaGY9R7rAULVRcat5FKJQ==", + "dev": true, + "requires": { + "@babel/helper-annotate-as-pure": "^7.12.13", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-optimise-call-expression": "^7.12.13", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-replace-supers": "^7.14.4", + "@babel/helper-split-export-declaration": "^7.12.13", + "globals": "^11.1.0" + } + }, + "@babel/plugin-transform-computed-properties": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-computed-properties/-/plugin-transform-computed-properties-7.13.0.tgz", + "integrity": "sha512-RRqTYTeZkZAz8WbieLTvKUEUxZlUTdmL5KGMyZj7FnMfLNKV4+r5549aORG/mgojRmFlQMJDUupwAMiF2Q7OUg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-destructuring": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-destructuring/-/plugin-transform-destructuring-7.14.4.tgz", + "integrity": "sha512-JyywKreTCGTUsL1OKu1A3ms/R1sTP0WxbpXlALeGzF53eB3bxtNkYdMj9SDgK7g6ImPy76J5oYYKoTtQImlhQA==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-dotall-regex": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-dotall-regex/-/plugin-transform-dotall-regex-7.12.13.tgz", + "integrity": "sha512-foDrozE65ZFdUC2OfgeOCrEPTxdB3yjqxpXh8CH+ipd9CHd4s/iq81kcUpyH8ACGNEPdFqbtzfgzbT/ZGlbDeQ==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-duplicate-keys": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-duplicate-keys/-/plugin-transform-duplicate-keys-7.12.13.tgz", + "integrity": "sha512-NfADJiiHdhLBW3pulJlJI2NB0t4cci4WTZ8FtdIuNc2+8pslXdPtRRAEWqUY+m9kNOk2eRYbTAOipAxlrOcwwQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-exponentiation-operator": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-exponentiation-operator/-/plugin-transform-exponentiation-operator-7.12.13.tgz", + "integrity": "sha512-fbUelkM1apvqez/yYx1/oICVnGo2KM5s63mhGylrmXUxK/IAXSIf87QIxVfZldWf4QsOafY6vV3bX8aMHSvNrA==", + "dev": true, + "requires": { + "@babel/helper-builder-binary-assignment-operator-visitor": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-for-of": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-for-of/-/plugin-transform-for-of-7.13.0.tgz", + "integrity": "sha512-IHKT00mwUVYE0zzbkDgNRP6SRzvfGCYsOxIRz8KsiaaHCcT9BWIkO+H9QRJseHBLOGBZkHUdHiqj6r0POsdytg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-function-name": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-function-name/-/plugin-transform-function-name-7.12.13.tgz", + "integrity": "sha512-6K7gZycG0cmIwwF7uMK/ZqeCikCGVBdyP2J5SKNCXO5EOHcqi+z7Jwf8AmyDNcBgxET8DrEtCt/mPKPyAzXyqQ==", + "dev": true, + "requires": { + "@babel/helper-function-name": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-literals": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-literals/-/plugin-transform-literals-7.12.13.tgz", + "integrity": "sha512-FW+WPjSR7hiUxMcKqyNjP05tQ2kmBCdpEpZHY1ARm96tGQCCBvXKnpjILtDplUnJ/eHZ0lALLM+d2lMFSpYJrQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-member-expression-literals": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-member-expression-literals/-/plugin-transform-member-expression-literals-7.12.13.tgz", + "integrity": "sha512-kxLkOsg8yir4YeEPHLuO2tXP9R/gTjpuTOjshqSpELUN3ZAg2jfDnKUvzzJxObun38sw3wm4Uu69sX/zA7iRvg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-modules-amd": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-amd/-/plugin-transform-modules-amd-7.14.2.tgz", + "integrity": "sha512-hPC6XBswt8P3G2D1tSV2HzdKvkqOpmbyoy+g73JG0qlF/qx2y3KaMmXb1fLrpmWGLZYA0ojCvaHdzFWjlmV+Pw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.2", + "@babel/helper-plugin-utils": "^7.13.0", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-commonjs": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-commonjs/-/plugin-transform-modules-commonjs-7.14.0.tgz", + "integrity": "sha512-EX4QePlsTaRZQmw9BsoPeyh5OCtRGIhwfLquhxGp5e32w+dyL8htOcDwamlitmNFK6xBZYlygjdye9dbd9rUlQ==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-simple-access": "^7.13.12", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-systemjs": { + "version": "7.13.8", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-systemjs/-/plugin-transform-modules-systemjs-7.13.8.tgz", + "integrity": "sha512-hwqctPYjhM6cWvVIlOIe27jCIBgHCsdH2xCJVAYQm7V5yTMoilbVMi9f6wKg0rpQAOn6ZG4AOyvCqFF/hUh6+A==", + "dev": true, + "requires": { + "@babel/helper-hoist-variables": "^7.13.0", + "@babel/helper-module-transforms": "^7.13.0", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-identifier": "^7.12.11", + "babel-plugin-dynamic-import-node": "^2.3.3" + } + }, + "@babel/plugin-transform-modules-umd": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-modules-umd/-/plugin-transform-modules-umd-7.14.0.tgz", + "integrity": "sha512-nPZdnWtXXeY7I87UZr9VlsWme3Y0cfFFE41Wbxz4bbaexAjNMInXPFUpRRUJ8NoMm0Cw+zxbqjdPmLhcjfazMw==", + "dev": true, + "requires": { + "@babel/helper-module-transforms": "^7.14.0", + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-named-capturing-groups-regex": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-named-capturing-groups-regex/-/plugin-transform-named-capturing-groups-regex-7.12.13.tgz", + "integrity": "sha512-Xsm8P2hr5hAxyYblrfACXpQKdQbx4m2df9/ZZSQ8MAhsadw06+jW7s9zsSw6he+mJZXRlVMyEnVktJo4zjk1WA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13" + } + }, + "@babel/plugin-transform-new-target": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-new-target/-/plugin-transform-new-target-7.12.13.tgz", + "integrity": "sha512-/KY2hbLxrG5GTQ9zzZSc3xWiOy379pIETEhbtzwZcw9rvuaVV4Fqy7BYGYOWZnaoXIQYbbJ0ziXLa/sKcGCYEQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-object-super": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-object-super/-/plugin-transform-object-super-7.12.13.tgz", + "integrity": "sha512-JzYIcj3XtYspZDV8j9ulnoMPZZnF/Cj0LUxPOjR89BdBVx+zYJI9MdMIlUZjbXDX+6YVeS6I3e8op+qQ3BYBoQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13", + "@babel/helper-replace-supers": "^7.12.13" + } + }, + "@babel/plugin-transform-parameters": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-parameters/-/plugin-transform-parameters-7.14.2.tgz", + "integrity": "sha512-NxoVmA3APNCC1JdMXkdYXuQS+EMdqy0vIwyDHeKHiJKRxmp1qGSdb0JLEIoPRhkx6H/8Qi3RJ3uqOCYw8giy9A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-property-literals": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-property-literals/-/plugin-transform-property-literals-7.12.13.tgz", + "integrity": "sha512-nqVigwVan+lR+g8Fj8Exl0UQX2kymtjcWfMOYM1vTYEKujeyv2SkMgazf2qNcK7l4SDiKyTA/nHCPqL4e2zo1A==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-regenerator": { + "version": "7.13.15", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-regenerator/-/plugin-transform-regenerator-7.13.15.tgz", + "integrity": "sha512-Bk9cOLSz8DiurcMETZ8E2YtIVJbFCPGW28DJWUakmyVWtQSm6Wsf0p4B4BfEr/eL2Nkhe/CICiUiMOCi1TPhuQ==", + "dev": true, + "requires": { + "regenerator-transform": "^0.14.2" + } + }, + "@babel/plugin-transform-reserved-words": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-reserved-words/-/plugin-transform-reserved-words-7.12.13.tgz", + "integrity": "sha512-xhUPzDXxZN1QfiOy/I5tyye+TRz6lA7z6xaT4CLOjPRMVg1ldRf0LHw0TDBpYL4vG78556WuHdyO9oi5UmzZBg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-shorthand-properties": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-shorthand-properties/-/plugin-transform-shorthand-properties-7.12.13.tgz", + "integrity": "sha512-xpL49pqPnLtf0tVluuqvzWIgLEhuPpZzvs2yabUHSKRNlN7ScYU7aMlmavOeyXJZKgZKQRBlh8rHbKiJDraTSw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-spread": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-spread/-/plugin-transform-spread-7.13.0.tgz", + "integrity": "sha512-V6vkiXijjzYeFmQTr3dBxPtZYLPcUfY34DebOU27jIl2M/Y8Egm52Hw82CSjjPqd54GTlJs5x+CR7HeNr24ckg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-skip-transparent-expression-wrappers": "^7.12.1" + } + }, + "@babel/plugin-transform-sticky-regex": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-sticky-regex/-/plugin-transform-sticky-regex-7.12.13.tgz", + "integrity": "sha512-Jc3JSaaWT8+fr7GRvQP02fKDsYk4K/lYwWq38r/UGfaxo89ajud321NH28KRQ7xy1Ybc0VUE5Pz8psjNNDUglg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-template-literals": { + "version": "7.13.0", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-template-literals/-/plugin-transform-template-literals-7.13.0.tgz", + "integrity": "sha512-d67umW6nlfmr1iehCcBv69eSUSySk1EsIS8aTDX4Xo9qajAh6mYtcl4kJrBkGXuxZPEgVr7RVfAvNW6YQkd4Mw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.13.0" + } + }, + "@babel/plugin-transform-typeof-symbol": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-typeof-symbol/-/plugin-transform-typeof-symbol-7.12.13.tgz", + "integrity": "sha512-eKv/LmUJpMnu4npgfvs3LiHhJua5fo/CysENxa45YCQXZwKnGCQKAg87bvoqSW1fFT+HA32l03Qxsm8ouTY3ZQ==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-unicode-escapes": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-escapes/-/plugin-transform-unicode-escapes-7.12.13.tgz", + "integrity": "sha512-0bHEkdwJ/sN/ikBHfSmOXPypN/beiGqjo+o4/5K+vxEFNPRPdImhviPakMKG4x96l85emoa0Z6cDflsdBusZbw==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/plugin-transform-unicode-regex": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/plugin-transform-unicode-regex/-/plugin-transform-unicode-regex-7.12.13.tgz", + "integrity": "sha512-mDRzSNY7/zopwisPZ5kM9XKCfhchqIYwAKRERtEnhYscZB79VRekuRSoYbN0+KVe3y8+q1h6A4svXtP7N+UoCA==", + "dev": true, + "requires": { + "@babel/helper-create-regexp-features-plugin": "^7.12.13", + "@babel/helper-plugin-utils": "^7.12.13" + } + }, + "@babel/preset-env": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/preset-env/-/preset-env-7.14.4.tgz", + "integrity": "sha512-GwMMsuAnDtULyOtuxHhzzuSRxFeP0aR/LNzrHRzP8y6AgDNgqnrfCCBm/1cRdTU75tRs28Eh76poHLcg9VF0LA==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.14.4", + "@babel/helper-compilation-targets": "^7.14.4", + "@babel/helper-plugin-utils": "^7.13.0", + "@babel/helper-validator-option": "^7.12.17", + "@babel/plugin-bugfix-v8-spread-parameters-in-optional-chaining": "^7.13.12", + "@babel/plugin-proposal-async-generator-functions": "^7.14.2", + "@babel/plugin-proposal-class-properties": "^7.13.0", + "@babel/plugin-proposal-class-static-block": "^7.14.3", + "@babel/plugin-proposal-dynamic-import": "^7.14.2", + "@babel/plugin-proposal-export-namespace-from": "^7.14.2", + "@babel/plugin-proposal-json-strings": "^7.14.2", + "@babel/plugin-proposal-logical-assignment-operators": "^7.14.2", + "@babel/plugin-proposal-nullish-coalescing-operator": "^7.14.2", + "@babel/plugin-proposal-numeric-separator": "^7.14.2", + "@babel/plugin-proposal-object-rest-spread": "^7.14.4", + "@babel/plugin-proposal-optional-catch-binding": "^7.14.2", + "@babel/plugin-proposal-optional-chaining": "^7.14.2", + "@babel/plugin-proposal-private-methods": "^7.13.0", + "@babel/plugin-proposal-private-property-in-object": "^7.14.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.12.13", + "@babel/plugin-syntax-async-generators": "^7.8.4", + "@babel/plugin-syntax-class-properties": "^7.12.13", + "@babel/plugin-syntax-class-static-block": "^7.12.13", + "@babel/plugin-syntax-dynamic-import": "^7.8.3", + "@babel/plugin-syntax-export-namespace-from": "^7.8.3", + "@babel/plugin-syntax-json-strings": "^7.8.3", + "@babel/plugin-syntax-logical-assignment-operators": "^7.10.4", + "@babel/plugin-syntax-nullish-coalescing-operator": "^7.8.3", + "@babel/plugin-syntax-numeric-separator": "^7.10.4", + "@babel/plugin-syntax-object-rest-spread": "^7.8.3", + "@babel/plugin-syntax-optional-catch-binding": "^7.8.3", + "@babel/plugin-syntax-optional-chaining": "^7.8.3", + "@babel/plugin-syntax-private-property-in-object": "^7.14.0", + "@babel/plugin-syntax-top-level-await": "^7.12.13", + "@babel/plugin-transform-arrow-functions": "^7.13.0", + "@babel/plugin-transform-async-to-generator": "^7.13.0", + "@babel/plugin-transform-block-scoped-functions": "^7.12.13", + "@babel/plugin-transform-block-scoping": "^7.14.4", + "@babel/plugin-transform-classes": "^7.14.4", + "@babel/plugin-transform-computed-properties": "^7.13.0", + "@babel/plugin-transform-destructuring": "^7.14.4", + "@babel/plugin-transform-dotall-regex": "^7.12.13", + "@babel/plugin-transform-duplicate-keys": "^7.12.13", + "@babel/plugin-transform-exponentiation-operator": "^7.12.13", + "@babel/plugin-transform-for-of": "^7.13.0", + "@babel/plugin-transform-function-name": "^7.12.13", + "@babel/plugin-transform-literals": "^7.12.13", + "@babel/plugin-transform-member-expression-literals": "^7.12.13", + "@babel/plugin-transform-modules-amd": "^7.14.2", + "@babel/plugin-transform-modules-commonjs": "^7.14.0", + "@babel/plugin-transform-modules-systemjs": "^7.13.8", + "@babel/plugin-transform-modules-umd": "^7.14.0", + "@babel/plugin-transform-named-capturing-groups-regex": "^7.12.13", + "@babel/plugin-transform-new-target": "^7.12.13", + "@babel/plugin-transform-object-super": "^7.12.13", + "@babel/plugin-transform-parameters": "^7.14.2", + "@babel/plugin-transform-property-literals": "^7.12.13", + "@babel/plugin-transform-regenerator": "^7.13.15", + "@babel/plugin-transform-reserved-words": "^7.12.13", + "@babel/plugin-transform-shorthand-properties": "^7.12.13", + "@babel/plugin-transform-spread": "^7.13.0", + "@babel/plugin-transform-sticky-regex": "^7.12.13", + "@babel/plugin-transform-template-literals": "^7.13.0", + "@babel/plugin-transform-typeof-symbol": "^7.12.13", + "@babel/plugin-transform-unicode-escapes": "^7.12.13", + "@babel/plugin-transform-unicode-regex": "^7.12.13", + "@babel/preset-modules": "^0.1.4", + "@babel/types": "^7.14.4", + "babel-plugin-polyfill-corejs2": "^0.2.0", + "babel-plugin-polyfill-corejs3": "^0.2.0", + "babel-plugin-polyfill-regenerator": "^0.2.0", + "core-js-compat": "^3.9.0", + "semver": "^6.3.0" + } + }, + "@babel/preset-modules": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/@babel/preset-modules/-/preset-modules-0.1.4.tgz", + "integrity": "sha512-J36NhwnfdzpmH41M1DrnkkgAqhZaqr/NBdPfQ677mLzlaXo+oDiv1deyCDtgAhz8p328otdob0Du7+xgHGZbKg==", + "dev": true, + "requires": { + "@babel/helper-plugin-utils": "^7.0.0", + "@babel/plugin-proposal-unicode-property-regex": "^7.4.4", + "@babel/plugin-transform-dotall-regex": "^7.4.4", + "@babel/types": "^7.4.4", + "esutils": "^2.0.2" + } + }, + "@babel/runtime": { + "version": "7.14.0", + "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.14.0.tgz", + "integrity": "sha512-JELkvo/DlpNdJ7dlyw/eY7E0suy5i5GQH+Vlxaq1nsNJ+H7f4Vtv3jMeCEgRhZZQFXTjldYfQgv2qmM6M1v5wA==", + "dev": true, + "requires": { + "regenerator-runtime": "^0.13.4" + } + }, + "@babel/template": { + "version": "7.12.13", + "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.12.13.tgz", + "integrity": "sha512-/7xxiGA57xMo/P2GVvdEumr8ONhFOhfgq2ihK3h1e6THqzTAkHbkXgB0xI9yeTfIUoH3+oAeHhqm/I43OTbbjA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/parser": "^7.12.13", + "@babel/types": "^7.12.13" + } + }, + "@babel/traverse": { + "version": "7.14.2", + "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.14.2.tgz", + "integrity": "sha512-TsdRgvBFHMyHOOzcP9S6QU0QQtjxlRpEYOy3mcCO5RgmC305ki42aSAmfZEMSSYBla2oZ9BMqYlncBaKmD/7iA==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.12.13", + "@babel/generator": "^7.14.2", + "@babel/helper-function-name": "^7.14.2", + "@babel/helper-split-export-declaration": "^7.12.13", + "@babel/parser": "^7.14.2", + "@babel/types": "^7.14.2", + "debug": "^4.1.0", + "globals": "^11.1.0" + } + }, + "@babel/types": { + "version": "7.14.4", + "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.14.4.tgz", + "integrity": "sha512-lCj4aIs0xUefJFQnwwQv2Bxg7Omd6bgquZ6LGC+gGMh6/s5qDVfjuCMlDmYQ15SLsWHd9n+X3E75lKIhl5Lkiw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.14.0", + "to-fast-properties": "^2.0.0" + } + }, + "@discoveryjs/json-ext": { + "version": "0.5.3", + "resolved": "https://registry.npmjs.org/@discoveryjs/json-ext/-/json-ext-0.5.3.tgz", + "integrity": "sha512-Fxt+AfXgjMoin2maPIYzFZnQjAXjAL0PHscM5pRTtatFqB+vZxAM9tLp2Optnuw3QOQC40jTNeGYFOMvyf7v9g==", + "dev": true + }, + "@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==", + "dev": true, + "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==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.7.tgz", + "integrity": "sha512-BTIhocbPBSrRmHxOAJFtR18oLhxTtAFDAvL8hY1S3iU8k+E60W/YFs4jrixGzQjMpF4qPXxIQHcjVD9dz1C2QA==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/eslint": { + "version": "7.2.13", + "resolved": "https://registry.npmjs.org/@types/eslint/-/eslint-7.2.13.tgz", + "integrity": "sha512-LKmQCWAlnVHvvXq4oasNUMTJJb2GwSyTY8+1C7OH5ILR8mPLaljv1jxL1bXW3xB3jFbQxTKxJAvI8PyjB09aBg==", + "dev": true, + "requires": { + "@types/estree": "*", + "@types/json-schema": "*" + } + }, + "@types/eslint-scope": { + "version": "3.7.0", + "resolved": "https://registry.npmjs.org/@types/eslint-scope/-/eslint-scope-3.7.0.tgz", + "integrity": "sha512-O/ql2+rrCUe2W2rs7wMR+GqPRcgB6UiqN5RhrR5xruFlY7l9YLMn0ZkDzjoHLeiFkR8MCQZVudUuuvQ2BLC9Qw==", + "dev": true, + "requires": { + "@types/eslint": "*", + "@types/estree": "*" + } + }, + "@types/estree": { + "version": "0.0.47", + "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.47.tgz", + "integrity": "sha512-c5ciR06jK8u9BstrmJyO97m+klJrrhCf9u3rLu3DEAJBirxRqSCvDQoYKmxuYwQI5SZChAWu+tq9oVlGRuzPAg==", + "dev": true + }, + "@types/json-schema": { + "version": "7.0.7", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.7.tgz", + "integrity": "sha512-cxWFQVseBm6O9Gbw1IWb8r6OS4OhSt3hPZLkFApLjM8TEXROBuQGLAH2i2gZpcXdLBIrpXuTDhH7Vbm1iXmNGA==", + "dev": true + }, + "@types/node": { + "version": "15.12.1", + "resolved": "https://registry.npmjs.org/@types/node/-/node-15.12.1.tgz", + "integrity": "sha512-zyxJM8I1c9q5sRMtVF+zdd13Jt6RU4r4qfhTd7lQubyThvLfx6yYekWSQjGCGV2Tkecgxnlpl/DNlb6Hg+dmEw==", + "dev": true + }, + "@webassemblyjs/ast": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ast/-/ast-1.11.0.tgz", + "integrity": "sha512-kX2W49LWsbthrmIRMbQZuQDhGtjyqXfEmmHyEi4XWnSZtPmxY0+3anPIzsnRb45VH/J55zlOfWvZuY47aJZTJg==", + "dev": true, + "requires": { + "@webassemblyjs/helper-numbers": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0" + } + }, + "@webassemblyjs/floating-point-hex-parser": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/floating-point-hex-parser/-/floating-point-hex-parser-1.11.0.tgz", + "integrity": "sha512-Q/aVYs/VnPDVYvsCBL/gSgwmfjeCb4LW8+TMrO3cSzJImgv8lxxEPM2JA5jMrivE7LSz3V+PFqtMbls3m1exDA==", + "dev": true + }, + "@webassemblyjs/helper-api-error": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-api-error/-/helper-api-error-1.11.0.tgz", + "integrity": "sha512-baT/va95eXiXb2QflSx95QGT5ClzWpGaa8L7JnJbgzoYeaA27FCvuBXU758l+KXWRndEmUXjP0Q5fibhavIn8w==", + "dev": true + }, + "@webassemblyjs/helper-buffer": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-buffer/-/helper-buffer-1.11.0.tgz", + "integrity": "sha512-u9HPBEl4DS+vA8qLQdEQ6N/eJQ7gT7aNvMIo8AAWvAl/xMrcOSiI2M0MAnMCy3jIFke7bEee/JwdX1nUpCtdyA==", + "dev": true + }, + "@webassemblyjs/helper-numbers": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-numbers/-/helper-numbers-1.11.0.tgz", + "integrity": "sha512-DhRQKelIj01s5IgdsOJMKLppI+4zpmcMQ3XboFPLwCpSNH6Hqo1ritgHgD0nqHeSYqofA6aBN/NmXuGjM1jEfQ==", + "dev": true, + "requires": { + "@webassemblyjs/floating-point-hex-parser": "1.11.0", + "@webassemblyjs/helper-api-error": "1.11.0", + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/helper-wasm-bytecode": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-bytecode/-/helper-wasm-bytecode-1.11.0.tgz", + "integrity": "sha512-MbmhvxXExm542tWREgSFnOVo07fDpsBJg3sIl6fSp9xuu75eGz5lz31q7wTLffwL3Za7XNRCMZy210+tnsUSEA==", + "dev": true + }, + "@webassemblyjs/helper-wasm-section": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/helper-wasm-section/-/helper-wasm-section-1.11.0.tgz", + "integrity": "sha512-3Eb88hcbfY/FCukrg6i3EH8H2UsD7x8Vy47iVJrP967A9JGqgBVL9aH71SETPx1JrGsOUVLo0c7vMCN22ytJew==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0" + } + }, + "@webassemblyjs/ieee754": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/ieee754/-/ieee754-1.11.0.tgz", + "integrity": "sha512-KXzOqpcYQwAfeQ6WbF6HXo+0udBNmw0iXDmEK5sFlmQdmND+tr773Ti8/5T/M6Tl/413ArSJErATd8In3B+WBA==", + "dev": true, + "requires": { + "@xtuc/ieee754": "^1.2.0" + } + }, + "@webassemblyjs/leb128": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/leb128/-/leb128-1.11.0.tgz", + "integrity": "sha512-aqbsHa1mSQAbeeNcl38un6qVY++hh8OpCOzxhixSYgbRfNWcxJNJQwe2rezK9XEcssJbbWIkblaJRwGMS9zp+g==", + "dev": true, + "requires": { + "@xtuc/long": "4.2.2" + } + }, + "@webassemblyjs/utf8": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/utf8/-/utf8-1.11.0.tgz", + "integrity": "sha512-A/lclGxH6SpSLSyFowMzO/+aDEPU4hvEiooCMXQPcQFPPJaYcPQNKGOCLUySJsYJ4trbpr+Fs08n4jelkVTGVw==", + "dev": true + }, + "@webassemblyjs/wasm-edit": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-edit/-/wasm-edit-1.11.0.tgz", + "integrity": "sha512-JHQ0damXy0G6J9ucyKVXO2j08JVJ2ntkdJlq1UTiUrIgfGMmA7Ik5VdC/L8hBK46kVJgujkBIoMtT8yVr+yVOQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/helper-wasm-section": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0", + "@webassemblyjs/wasm-opt": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0", + "@webassemblyjs/wast-printer": "1.11.0" + } + }, + "@webassemblyjs/wasm-gen": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-gen/-/wasm-gen-1.11.0.tgz", + "integrity": "sha512-BEUv1aj0WptCZ9kIS30th5ILASUnAPEvE3tVMTrItnZRT9tXCLW2LEXT8ezLw59rqPP9klh9LPmpU+WmRQmCPQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/ieee754": "1.11.0", + "@webassemblyjs/leb128": "1.11.0", + "@webassemblyjs/utf8": "1.11.0" + } + }, + "@webassemblyjs/wasm-opt": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-opt/-/wasm-opt-1.11.0.tgz", + "integrity": "sha512-tHUSP5F4ywyh3hZ0+fDQuWxKx3mJiPeFufg+9gwTpYp324mPCQgnuVKwzLTZVqj0duRDovnPaZqDwoyhIO8kYg==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-buffer": "1.11.0", + "@webassemblyjs/wasm-gen": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0" + } + }, + "@webassemblyjs/wasm-parser": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wasm-parser/-/wasm-parser-1.11.0.tgz", + "integrity": "sha512-6L285Sgu9gphrcpDXINvm0M9BskznnzJTE7gYkjDbxET28shDqp27wpruyx3C2S/dvEwiigBwLA1cz7lNUi0kw==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/helper-api-error": "1.11.0", + "@webassemblyjs/helper-wasm-bytecode": "1.11.0", + "@webassemblyjs/ieee754": "1.11.0", + "@webassemblyjs/leb128": "1.11.0", + "@webassemblyjs/utf8": "1.11.0" + } + }, + "@webassemblyjs/wast-printer": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/@webassemblyjs/wast-printer/-/wast-printer-1.11.0.tgz", + "integrity": "sha512-Fg5OX46pRdTgB7rKIUojkh9vXaVN6sGYCnEiJN1GYkb0RPwShZXp6KTDqmoMdQPKhcroOXh3fEzmkWmCYaKYhQ==", + "dev": true, + "requires": { + "@webassemblyjs/ast": "1.11.0", + "@xtuc/long": "4.2.2" + } + }, "@webosose/ares-cli": { "version": "2.1.0", "resolved": "https://registry.npmjs.org/@webosose/ares-cli/-/ares-cli-2.1.0.tgz", @@ -3810,177 +5083,1677 @@ } } }, - "has-values": { - "version": "0.1.4", - "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", - "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + "has-values": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/has-values/-/has-values-0.1.4.tgz", + "integrity": "sha1-bWHeldkd/Km5oCCJrThL/49it3E=" + } + } + }, + "uri-js": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", + "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", + "requires": { + "punycode": "^2.1.0" + } + }, + "urix": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", + "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" + }, + "use": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", + "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, + "utils-merge": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", + "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", + "dev": true + }, + "uuid": { + "version": "3.4.0", + "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", + "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", + "dev": true + }, + "v8-compile-cache": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", + "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==" + }, + "validate-npm-package-license": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", + "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "requires": { + "spdx-correct": "^3.0.0", + "spdx-expression-parse": "^3.0.0" + } + }, + "vary": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", + "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "dev": true + }, + "verror": { + "version": "1.10.0", + "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", + "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + "dev": true, + "requires": { + "assert-plus": "^1.0.0", + "core-util-is": "1.0.2", + "extsprintf": "^1.2.0" + } + }, + "wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "dev": true, + "optional": true, + "requires": { + "defaults": "^1.0.3" + } + }, + "which": { + "version": "1.3.1", + "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", + "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", + "requires": { + "isexe": "^2.0.0" + } + }, + "wide-align": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", + "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + "dev": true, + "requires": { + "string-width": "^1.0.2 || 2" + }, + "dependencies": { + "ansi-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", + "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", + "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", + "dev": true + }, + "string-width": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", + "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", + "dev": true, + "requires": { + "is-fullwidth-code-point": "^2.0.0", + "strip-ansi": "^4.0.0" + } + }, + "strip-ansi": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", + "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", + "dev": true, + "requires": { + "ansi-regex": "^3.0.0" + } } } }, - "uri-js": { - "version": "4.4.0", - "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.0.tgz", - "integrity": "sha512-B0yRTzYdUCCn9n+F4+Gh4yIDtMQcaJsmYBDsTSG8g/OejKBodLQ2IHfN3bM7jUsRXndopT7OIXWdYqc1fjmV6g==", - "requires": { - "punycode": "^2.1.0" - } - }, - "urix": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/urix/-/urix-0.1.0.tgz", - "integrity": "sha1-2pN/emLiH+wf0Y1Js1wpNQZ6bHI=" - }, - "use": { - "version": "3.1.1", - "resolved": "https://registry.npmjs.org/use/-/use-3.1.1.tgz", - "integrity": "sha512-cwESVXlO3url9YWlFW/TA9cshCEhtu7IKJ/p5soJ/gGpj7vbvFrAY/eIioQ6Dw23KjZhYgiIo8HOs1nQ2vr/oQ==" + "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==" }, - "util-deprecate": { + "wrappy": { "version": "1.0.2", - "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", - "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", - "dev": true - }, - "utils-merge": { - "version": "1.0.1", - "resolved": "https://registry.npmjs.org/utils-merge/-/utils-merge-1.0.1.tgz", - "integrity": "sha1-n5VxD1CiZ5R7LMwSR0HBAoQn5xM=", - "dev": true - }, - "uuid": { - "version": "3.4.0", - "resolved": "https://registry.npmjs.org/uuid/-/uuid-3.4.0.tgz", - "integrity": "sha512-HjSDRw6gZE5JMggctHBcjVak08+KEVhSIiDzFnT9S9aegmp85S/bReBVTb4QTFaRNptJ9kuYaNhnbNEOkbKb/A==", - "dev": true + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" }, - "v8-compile-cache": { - "version": "2.2.0", - "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz", - "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==" + "write": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", + "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "requires": { + "mkdirp": "^0.5.1" + } }, - "validate-npm-package-license": { - "version": "3.0.4", - "resolved": "https://registry.npmjs.org/validate-npm-package-license/-/validate-npm-package-license-3.0.4.tgz", - "integrity": "sha512-DpKm2Ui/xN7/HQKCtpZxoRWBhZ9Z0kqtygG8XCgNQ8ZlDnxuQmWhj566j8fN4Cu3/JmbhsDo7fcAJq4s9h27Ew==", + "yauzl": { + "version": "2.10.0", + "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", + "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "dev": true, "requires": { - "spdx-correct": "^3.0.0", - "spdx-expression-parse": "^3.0.0" + "buffer-crc32": "~0.2.3", + "fd-slicer": "~1.1.0" + }, + "dependencies": { + "buffer-crc32": { + "version": "0.2.13", + "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", + "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", + "dev": true + } + } + } + } + }, + "@webpack-cli/configtest": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@webpack-cli/configtest/-/configtest-1.0.3.tgz", + "integrity": "sha512-WQs0ep98FXX2XBAfQpRbY0Ma6ADw8JR6xoIkaIiJIzClGOMqVRvPCWqndTxf28DgFopWan0EKtHtg/5W1h0Zkw==", + "dev": true + }, + "@webpack-cli/info": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/@webpack-cli/info/-/info-1.2.4.tgz", + "integrity": "sha512-ogE2T4+pLhTTPS/8MM3IjHn0IYplKM4HbVNMCWA9N4NrdPzunwenpCsqKEXyejMfRu6K8mhauIPYf8ZxWG5O6g==", + "dev": true, + "requires": { + "envinfo": "^7.7.3" + } + }, + "@webpack-cli/serve": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/@webpack-cli/serve/-/serve-1.4.0.tgz", + "integrity": "sha512-xgT/HqJ+uLWGX+Mzufusl3cgjAcnqYYskaB7o0vRcwOEfuu6hMzSILQpnIzFMGsTaeaX4Nnekl+6fadLbl1/Vg==", + "dev": true + }, + "@xtuc/ieee754": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/@xtuc/ieee754/-/ieee754-1.2.0.tgz", + "integrity": "sha512-DX8nKgqcGwsc0eJSqYt5lwP4DH5FlHnmuWWBRy7X0NcaGR0ZtuyeESgMwTYVEtxmsNGY+qit4QYT/MIYTOTPeA==", + "dev": true + }, + "@xtuc/long": { + "version": "4.2.2", + "resolved": "https://registry.npmjs.org/@xtuc/long/-/long-4.2.2.tgz", + "integrity": "sha512-NuHqBY1PB/D8xU6s/thBgOAiAP7HOYDQ32+BFZILJ8ivkUkAHQnWfn6WhL79Owj1qmUnoN/YPhktdIoucipkAQ==", + "dev": true + }, + "acorn": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.3.0.tgz", + "integrity": "sha512-tqPKHZ5CaBJw0Xmy0ZZvLs1qTV+BNFSyvn77ASXkpBNfIRk8ev26fKrD9iLGwGA9zedPao52GSHzq8lyZG0NUw==", + "dev": true + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "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" + } + }, + "ajv-keywords": { + "version": "3.5.2", + "resolved": "https://registry.npmjs.org/ajv-keywords/-/ajv-keywords-3.5.2.tgz", + "integrity": "sha512-5p6WTN0DdTGVQk6VjcEju19IgaHudalcfabD7yhDGeA6bcQnmL+CpveLJq/3hvfwd1aof6L386Ougkx6RfyMIQ==", + "dev": true + }, + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "array-union": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/array-union/-/array-union-2.1.0.tgz", + "integrity": "sha512-HGyxoOTYUyCM6stUe6EJgnd4EoewAI7zMdfqO+kGjnlZmBDz/cR5pf8r/cR4Wq60sL/p0IkcjUEEPwS3GFrIyw==", + "dev": true + }, + "babel-loader": { + "version": "8.2.2", + "resolved": "https://registry.npmjs.org/babel-loader/-/babel-loader-8.2.2.tgz", + "integrity": "sha512-JvTd0/D889PQBtUXJ2PXaKU/pjZDMtHA9V2ecm+eNRmmBCMR09a+fmpGTNwnJtFmFl5Ei7Vy47LjBb+L0wQ99g==", + "dev": true, + "requires": { + "find-cache-dir": "^3.3.1", + "loader-utils": "^1.4.0", + "make-dir": "^3.1.0", + "schema-utils": "^2.6.5" + } + }, + "babel-plugin-dynamic-import-node": { + "version": "2.3.3", + "resolved": "https://registry.npmjs.org/babel-plugin-dynamic-import-node/-/babel-plugin-dynamic-import-node-2.3.3.tgz", + "integrity": "sha512-jZVI+s9Zg3IqA/kdi0i6UDCybUI3aSBLnglhYbSSjKlV7yF1F/5LWv8MakQmvYpnbJDS6fcBL2KzHSxNCMtWSQ==", + "dev": true, + "requires": { + "object.assign": "^4.1.0" + } + }, + "babel-plugin-polyfill-corejs2": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs2/-/babel-plugin-polyfill-corejs2-0.2.2.tgz", + "integrity": "sha512-kISrENsJ0z5dNPq5eRvcctITNHYXWOA4DUZRFYCz3jYCcvTb/A546LIddmoGNMVYg2U38OyFeNosQwI9ENTqIQ==", + "dev": true, + "requires": { + "@babel/compat-data": "^7.13.11", + "@babel/helper-define-polyfill-provider": "^0.2.2", + "semver": "^6.1.1" + } + }, + "babel-plugin-polyfill-corejs3": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-corejs3/-/babel-plugin-polyfill-corejs3-0.2.2.tgz", + "integrity": "sha512-l1Cf8PKk12eEk5QP/NQ6TH8A1pee6wWDJ96WjxrMXFLHLOBFzYM4moG80HFgduVhTqAFez4alnZKEhP/bYHg0A==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2", + "core-js-compat": "^3.9.1" + } + }, + "babel-plugin-polyfill-regenerator": { + "version": "0.2.2", + "resolved": "https://registry.npmjs.org/babel-plugin-polyfill-regenerator/-/babel-plugin-polyfill-regenerator-0.2.2.tgz", + "integrity": "sha512-Goy5ghsc21HgPDFtzRkSirpZVW35meGoTmTOb2bxqdl60ghub4xOidgNTHaZfQ2FaxQsKmwvXtOAkcIS4SMBWg==", + "dev": true, + "requires": { + "@babel/helper-define-polyfill-provider": "^0.2.2" + } + }, + "big.js": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/big.js/-/big.js-5.2.2.tgz", + "integrity": "sha512-vyL2OymJxmarO8gxMr0mhChsO9QGwhynfuu4+MHTAW6czfq9humCB7rKpUjDd9YUiDPU4mzpyupFSvOClAwbmQ==", + "dev": true + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.16.6", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.16.6.tgz", + "integrity": "sha512-Wspk/PqO+4W9qp5iUTJsa1B/QrYn1keNCcEP5OvP7WBwT4KaDly0uONYmC6Xa3Z5IqnUgS0KcgLYu1l74x0ZXQ==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001219", + "colorette": "^1.2.2", + "electron-to-chromium": "^1.3.723", + "escalade": "^3.1.1", + "node-releases": "^1.1.71" + } + }, + "buffer-from": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/buffer-from/-/buffer-from-1.1.1.tgz", + "integrity": "sha512-MQcXEUbCKtEo7bhqEs6560Hyd4XaovZlO/k9V3hjVUF/zwW7KBVdSK4gIt/bzwS9MbR5qob+F5jusZsb0YQK2A==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "caniuse-lite": { + "version": "1.0.30001234", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001234.tgz", + "integrity": "sha512-a3gjUVKkmwLdNysa1xkUAwN2VfJUJyVW47rsi3aCbkRCtbHAfo+rOsCqVw29G6coQ8gzAPb5XBXwiGHwme3isA==", + "dev": true + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "chrome-trace-event": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/chrome-trace-event/-/chrome-trace-event-1.0.3.tgz", + "integrity": "sha512-p3KULyQg4S7NIHixdwbGX+nFHkoBiA4YQmyWtjb8XngSKV124nJmRysgAeujbUVb15vh+RvFUfCPqU7rXk+hZg==", + "dev": true + }, + "clone-deep": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/clone-deep/-/clone-deep-4.0.1.tgz", + "integrity": "sha512-neHB9xuzh/wk0dIHweyAXv2aPGZIVk3pLMe+/RNzINf17fe0OG96QroktYAUm7SM1PBnzTabaLboqqxDyMU+SQ==", + "dev": true, + "requires": { + "is-plain-object": "^2.0.4", + "kind-of": "^6.0.2", + "shallow-clone": "^3.0.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha1-p9BVi9icQveV3UIyj3QIMcpTvCU=", + "dev": true + }, + "colorette": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/colorette/-/colorette-1.2.2.tgz", + "integrity": "sha512-MKGMzyfeuutC/ZJ1cba9NqcNpfeqMUcYmyF1ZFY6/Cn7CNSAKx6a+s48sqLqyAiZuaP2TcqMhoo+dlwFnVxT9w==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "commondir": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/commondir/-/commondir-1.0.1.tgz", + "integrity": "sha1-3dgA2gxmEnOTzKWVDqloo6rxJTs=", + "dev": true + }, + "convert-source-map": { + "version": "1.7.0", + "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.7.0.tgz", + "integrity": "sha512-4FJkXzKXEDB1snCFZlLP4gpC3JILicCpGbzG9f9G7tGqGCzETQ2hWPrcinA9oU4wtf2biUaEH5065UnMeR33oA==", + "dev": true, + "requires": { + "safe-buffer": "~5.1.1" + } + }, + "copy-webpack-plugin": { + "version": "9.0.0", + "resolved": "https://registry.npmjs.org/copy-webpack-plugin/-/copy-webpack-plugin-9.0.0.tgz", + "integrity": "sha512-k8UB2jLIb1Jip2nZbCz83T/XfhfjX6mB1yLJNYKrpYi7FQimfOoFv/0//iT6HV1K8FwUB5yUbCcnpLebJXJTug==", + "dev": true, + "requires": { + "fast-glob": "^3.2.5", + "glob-parent": "^6.0.0", + "globby": "^11.0.3", + "normalize-path": "^3.0.0", + "p-limit": "^3.1.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" } }, - "vary": { - "version": "1.1.2", - "resolved": "https://registry.npmjs.org/vary/-/vary-1.1.2.tgz", - "integrity": "sha1-IpnwLG3tMNSllhsLn3RSShj2NPw=", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, + "core-js-compat": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.13.1.tgz", + "integrity": "sha512-mdrcxc0WznfRd8ZicEZh1qVeJ2mu6bwQFh8YVUK48friy/FOwFV5EJj9/dlh+nMQ74YusdVfBFDuomKgUspxWQ==", + "dev": true, + "requires": { + "browserslist": "^4.16.6", + "semver": "7.0.0" + }, + "dependencies": { + "semver": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.0.0.tgz", + "integrity": "sha512-+GB6zVA9LWh6zovYQLALHwv5rb2PHGlJi3lfiqIHxR0uuwCgefcOJc59v9fv1w8GbStwxuuqqAjI9NMAOOgq1A==", "dev": true - }, - "verror": { - "version": "1.10.0", - "resolved": "https://registry.npmjs.org/verror/-/verror-1.10.0.tgz", - "integrity": "sha1-OhBcoXBTr1XW4nDB+CiGguGNpAA=", + } + } + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "debug": { + "version": "4.3.1", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", + "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "define-properties": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.3.tgz", + "integrity": "sha512-3MqfYKj2lLzdMSf8ZIZE/V+Zuy+BgD6f164e8K2w7dgnpKArBDerGYpM46IYYcjnkdPNMjPk9A6VFB8+3SKlXQ==", + "dev": true, + "requires": { + "object-keys": "^1.0.12" + } + }, + "dir-glob": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/dir-glob/-/dir-glob-3.0.1.tgz", + "integrity": "sha512-WkrWp9GR4KXfKGYzOLmTuGVi1UWFfws377n9cc55/tb6DuqyF6pcQ5AbiHEshaDpY9v6oaSr2XCDidGmMwdzIA==", + "dev": true, + "requires": { + "path-type": "^4.0.0" + } + }, + "electron-to-chromium": { + "version": "1.3.748", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.3.748.tgz", + "integrity": "sha512-fmIKfYALVeEybk/L2ucdgt7jN3JsbGtg3K9pmF/MRWgkeADBI1VSAa5IzdG2gZwTxsnsrFtdMpOTSM5mrBRKVQ==", + "dev": true + }, + "emojis-list": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/emojis-list/-/emojis-list-3.0.0.tgz", + "integrity": "sha512-/kyM18EfinwXZbno9FyUGeFh87KC8HRQBQGildHZbEuRyWFOmv1U10o9BBp8XVZDVNNuQKyIGIu5ZYAAXJ0V2Q==", + "dev": true + }, + "enhanced-resolve": { + "version": "5.8.2", + "resolved": "https://registry.npmjs.org/enhanced-resolve/-/enhanced-resolve-5.8.2.tgz", + "integrity": "sha512-F27oB3WuHDzvR2DOGNTaYy0D5o0cnrv8TeI482VM4kYgQd/FT9lUQwuNsJ0oOHtBUq7eiW5ytqzp7nBFknL+GA==", + "dev": true, + "requires": { + "graceful-fs": "^4.2.4", + "tapable": "^2.2.0" + } + }, + "envinfo": { + "version": "7.8.1", + "resolved": "https://registry.npmjs.org/envinfo/-/envinfo-7.8.1.tgz", + "integrity": "sha512-/o+BXHmB7ocbHEAs6F2EnG0ogybVVUdkRunTT2glZU9XAaGmhqskrvKwqXuDfNjEO0LZKWdejEEpnq8aM0tOaw==", + "dev": true + }, + "es-module-lexer": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/es-module-lexer/-/es-module-lexer-0.4.1.tgz", + "integrity": "sha512-ooYciCUtfw6/d2w56UVeqHPcoCFAiJdz5XOkYpv/Txl1HMUozpXjz/2RIQgqwKdXNDPSF1W7mJCFse3G+HDyAA==", + "dev": true + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha1-G2HAViGQqN/2rjuyzwIAyhMLhtQ=", + "dev": true + }, + "eslint-scope": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz", + "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^4.1.1" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + }, + "dependencies": { + "estraverse": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.2.0.tgz", + "integrity": "sha512-BxbNGGNm0RyRYvUdHpIwv9IWzeM9XClbOxwoATuFdOE7ZE6wHL+HQ5T8hoPM+zHvmKzzsEqhgy0GrQ5X13afiQ==", + "dev": true + } + } + }, + "estraverse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz", + "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==", + "dev": true + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "events": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/events/-/events-3.3.0.tgz", + "integrity": "sha512-mQw+2fkQbALzQ7V0MY0IqdnXNOeTtP4r0lN9z7AAawCXgqea7bDii20AYrIBrFd/Hx0M2Ocz6S111CaFkUcb0Q==", + "dev": true + }, + "execa": { + "version": "5.1.1", + "resolved": "https://registry.npmjs.org/execa/-/execa-5.1.1.tgz", + "integrity": "sha512-8uSpZZocAZRBAPIEINJj3Lo9HyGitllczc27Eh5YYojjMFMn8yHMDMaUHE2Jqfq05D/wucwI4JGURyXt1vchyg==", + "dev": true, + "requires": { + "cross-spawn": "^7.0.3", + "get-stream": "^6.0.0", + "human-signals": "^2.1.0", + "is-stream": "^2.0.0", + "merge-stream": "^2.0.0", + "npm-run-path": "^4.0.1", + "onetime": "^5.1.2", + "signal-exit": "^3.0.3", + "strip-final-newline": "^2.0.0" + } + }, + "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==", + "dev": true + }, + "fast-glob": { + "version": "3.2.5", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.2.5.tgz", + "integrity": "sha512-2DtFcgT68wiTTiwZ2hNdJfcHNke9XOfnwmBRWXhmeKM8rF0TGwmC/Qto3S7RoZKp5cilZbxzO5iTNTQsJ+EeDg==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.0", + "merge2": "^1.3.0", + "micromatch": "^4.0.2", + "picomatch": "^2.2.1" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "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==", + "dev": true + }, + "fastest-levenshtein": { + "version": "1.0.12", + "resolved": "https://registry.npmjs.org/fastest-levenshtein/-/fastest-levenshtein-1.0.12.tgz", + "integrity": "sha512-On2N+BpYJ15xIC974QNVuYGMOlEVt4s0EOI3wwMqOmK1fdDY+FN/zltPV8vosq4ad4c/gJ1KHScUn/6AWIgiow==", + "dev": true + }, + "fastq": { + "version": "1.11.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.11.0.tgz", + "integrity": "sha512-7Eczs8gIPDrVzT+EksYBcupqMyxSHXXrHOLRRxU2/DicV8789MRBRR8+Hc2uWzUupOs4YS4JzBmBxjjCVBxD/g==", + "dev": true, + "requires": { + "reusify": "^1.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-cache-dir": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/find-cache-dir/-/find-cache-dir-3.3.1.tgz", + "integrity": "sha512-t2GDMt3oGC/v+BMwzmllWDuJF/xcDtE5j/fCGbqDD7OLuJkj0cfh1YSA5VKPvwMeLFLNDBkwOKZ2X85jGLVftQ==", + "dev": true, + "requires": { + "commondir": "^1.0.1", + "make-dir": "^3.0.2", + "pkg-dir": "^4.1.0" + } + }, + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "dev": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "gensync": { + "version": "1.0.0-beta.2", + "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz", + "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==", + "dev": true + }, + "get-intrinsic": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.1.tgz", + "integrity": "sha512-kWZrnVM42QCiEA2Ig1bG8zjoIMOgxWwYCEeNdwY6Tv/cOSeGpcoX4pXHfKUxNKVoArnrEr2e9srnAxxGIraS9Q==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-symbols": "^1.0.1" + } + }, + "get-stream": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-6.0.1.tgz", + "integrity": "sha512-ts6Wi+2j3jQjqi70w5AlN8DFnkSwC+MqmxEzdEALB2qXZYV3X/b1CTfgPLGJNMeAWxdPfU8FO1ms3NUfaHCPYg==", + "dev": true + }, + "glob-parent": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.0.tgz", + "integrity": "sha512-Hdd4287VEJcZXUwv1l8a+vXC1GjOQqXe+VS30w/ypihpcnu9M1n3xeYeJu5CBpeEQj2nAab2xxz28GuA3vp4Ww==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + }, + "glob-to-regexp": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/glob-to-regexp/-/glob-to-regexp-0.4.1.tgz", + "integrity": "sha512-lkX1HJXwyMcprw/5YUZc2s7DrpAiHB21/V+E1rHUrVNokkvB6bqMzT0VfV6/86ZNabt1k14YOIaT7nDvOX3Iiw==", + "dev": true + }, + "globals": { + "version": "11.12.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz", + "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==", + "dev": true + }, + "globby": { + "version": "11.0.3", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.0.3.tgz", + "integrity": "sha512-ffdmosjA807y7+lA1NM0jELARVmYul/715xiILEjo3hBLPTcirgQNnXECn5g3mtR8TOLCVbkfua1Hpen25/Xcg==", + "dev": true, + "requires": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.1.1", + "ignore": "^5.1.4", + "merge2": "^1.3.0", + "slash": "^3.0.0" + } + }, + "graceful-fs": { + "version": "4.2.6", + "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.6.tgz", + "integrity": "sha512-nTnJ528pbqxYanhpDYsi4Rd8MAeaBA67+RZ10CM1m3bTAVFEDcd5AuA4a6W5YkGZ1iNXHzZz8T6TBKLeBuNriQ==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha1-tdRU3CGZriJWmfNGfloH87lVuv0=", + "dev": true + }, + "has-symbols": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.2.tgz", + "integrity": "sha512-chXa79rL/UC2KlX17jo3vRGz0azaWEx5tGqZg5pO3NUyEJVB17dMruQlzCCOfUvElghKcm5194+BCRvi2Rv/Gw==", + "dev": true + }, + "human-signals": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/human-signals/-/human-signals-2.1.0.tgz", + "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", + "dev": true + }, + "ignore": { + "version": "5.1.8", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", + "integrity": "sha512-BMpfD7PpiETpBl/A6S498BaIJ6Y/ABT93ETbby2fP00v4EbvPBXWEoaR1UBPKs3iR53pJY7EtZk5KACI57i1Uw==", + "dev": true + }, + "import-local": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/import-local/-/import-local-3.0.2.tgz", + "integrity": "sha512-vjL3+w0oulAVZ0hBHnxa/Nm5TAurf9YLQJDhqRZyqb+VKGOB6LU8t9H1Nr5CIo16vh9XfJTOoHwU0B71S557gA==", + "dev": true, + "requires": { + "pkg-dir": "^4.2.0", + "resolve-cwd": "^3.0.0" + } + }, + "interpret": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/interpret/-/interpret-2.2.0.tgz", + "integrity": "sha512-Ju0Bz/cEia55xDwUWEa8+olFpCiQoypjnQySseKtmjNrnps3P+xfpUmGr90T7yjlVJmOtybRvPXhKMbHr+fWnw==", + "dev": true + }, + "is-core-module": { + "version": "2.4.0", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.4.0.tgz", + "integrity": "sha512-6A2fkfq1rfeQZjxrZJGerpLCTHRNEBiSgnu0+obeJpEPZRUooHgsizvzv0ZjJwOz3iWIHdJtVWJ/tmPr3D21/A==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha1-qIwCU1eR8C7TfHahueqXc8gz+MI=", + "dev": true + }, + "is-glob": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.1.tgz", + "integrity": "sha512-5G0tKtBTFImOqDnLB2hG6Bp2qcKEFduo4tZu9MT/H6NQv/ghhy30o55ufafxJ/LdH79LLs2Kfrn85TLKyA7BUg==", + "dev": true, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-plain-object": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/is-plain-object/-/is-plain-object-2.0.4.tgz", + "integrity": "sha512-h5PpgXkWitc38BBMYawTYMWJHFZJVnBquFE57xFpjB8pJFiF6gZ+bU+WyI/yqXiFR5mdLsgYNaPe8uao6Uv9Og==", + "dev": true, + "requires": { + "isobject": "^3.0.1" + } + }, + "is-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/is-stream/-/is-stream-2.0.0.tgz", + "integrity": "sha512-XCoy+WlUr7d1+Z8GgSuXmpuUFC9fOhRXglJMx+dwLKTkL44Cjd4W1Z5P+BQZpr+cR93aGP4S/s7Ftw6Nd/kiEw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha1-6PvzdNxVb/iUehDcsFctYz8s+hA=", + "dev": true + }, + "isobject": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/isobject/-/isobject-3.0.1.tgz", + "integrity": "sha1-TkMekrEalzFjaqH5yNHMvP2reN8=", + "dev": true + }, + "jest-worker": { + "version": "27.0.2", + "resolved": "https://registry.npmjs.org/jest-worker/-/jest-worker-27.0.2.tgz", + "integrity": "sha512-EoBdilOTTyOgmHXtw/cPc+ZrCA0KJMrkXzkrPGNwLmnvvlN1nj7MPrxpT7m+otSv2e1TLaVffzDnE/LB14zJMg==", + "dev": true, + "requires": { + "@types/node": "*", + "merge-stream": "^2.0.0", + "supports-color": "^8.0.0" + }, + "dependencies": { + "has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true + }, + "supports-color": { + "version": "8.1.1", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz", + "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==", "dev": true, "requires": { - "assert-plus": "^1.0.0", - "core-util-is": "1.0.2", - "extsprintf": "^1.2.0" + "has-flag": "^4.0.0" } - }, - "wcwidth": { + } + } + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "jsesc": { + "version": "2.5.2", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz", + "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==", + "dev": true + }, + "json-parse-better-errors": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/json-parse-better-errors/-/json-parse-better-errors-1.0.2.tgz", + "integrity": "sha512-mrqyZKfX5EhL7hvqcV6WG1yYjnjeuYDzDhhcAAUrq8Po85NBQBJP+ZDUT75qZQ98IkUoBqdkExkukOU7Ts2wrw==", + "dev": true + }, + "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==", + "dev": true + }, + "json5": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.0.tgz", + "integrity": "sha512-f+8cldu7X/y7RAJurMEJmdoKXGB/X550w2Nr3tTbezL6RwEE/iMcm+tZnXeoZtKuOq6ft8+CqzEkrIgx1fPoQA==", + "dev": true, + "requires": { + "minimist": "^1.2.5" + } + }, + "kind-of": { + "version": "6.0.3", + "resolved": "https://registry.npmjs.org/kind-of/-/kind-of-6.0.3.tgz", + "integrity": "sha512-dcS1ul+9tmeD95T+x28/ehLgd9mENa3LsvDTtzm3vyBEO7RPptvAD+t44WVXaUjTBRcrpFeFlC8WCruUR456hw==", + "dev": true + }, + "loader-runner": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/loader-runner/-/loader-runner-4.2.0.tgz", + "integrity": "sha512-92+huvxMvYlMzMt0iIOukcwYBFpkYJdpl2xsZ7LrlayO7E8SOv+JJUEK17B/dJIHAOLMfh2dZZ/Y18WgmGtYNw==", + "dev": true + }, + "loader-utils": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-1.4.0.tgz", + "integrity": "sha512-qH0WSMBtn/oHuwjy/NucEgbx5dbxxnxup9s4PVXJUDHZBQY+s0NWA9rJf53RBnQZxfch7euUui7hpoAPvALZdA==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^1.0.1" + }, + "dependencies": { + "json5": { "version": "1.0.1", - "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", - "integrity": "sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g=", + "resolved": "https://registry.npmjs.org/json5/-/json5-1.0.1.tgz", + "integrity": "sha512-aKS4WQjPenRxiQsC93MNfjx+nbF4PAdYzmd/1JIj8HYzqfbu86beTuNgXDzPknWk0n0uARlyewZo4s++ES36Ow==", "dev": true, - "optional": true, - "requires": { - "defaults": "^1.0.3" - } - }, - "which": { - "version": "1.3.1", - "resolved": "https://registry.npmjs.org/which/-/which-1.3.1.tgz", - "integrity": "sha512-HxJdYWq1MTIQbJ3nw0cqssHoTNU267KlrDuGZ1WYlxDStUtKUhOaJmh112/TZmHxxUfuJqPXSOm7tDyas0OSIQ==", "requires": { - "isexe": "^2.0.0" + "minimist": "^1.2.0" } - }, - "wide-align": { - "version": "1.1.3", - "resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.3.tgz", - "integrity": "sha512-QGkOQc8XL6Bt5PwnsExKBPuMKBxnGxWWW3fU55Xt4feHozMUhdUMaBCk290qpm/wG5u/RSKzwdAC4i51YigihA==", + } + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "dev": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "lodash.debounce": { + "version": "4.0.8", + "resolved": "https://registry.npmjs.org/lodash.debounce/-/lodash.debounce-4.0.8.tgz", + "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", + "dev": true + }, + "make-dir": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", + "integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==", + "dev": true, + "requires": { + "semver": "^6.0.0" + } + }, + "merge-stream": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/merge-stream/-/merge-stream-2.0.0.tgz", + "integrity": "sha512-abv/qOcuPfk3URPfDzmZU1LKmuw8kT+0nIHvKrKgFrwifol/doWcdA4ZqsWQ8ENrFKkd67Mfpo/LovbIUsbt3w==", + "dev": true + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.4.tgz", + "integrity": "sha512-pRmzw/XUcwXGpD9aI9q/0XOwLNygjETJ8y0ao0wdqprrzDa4YnxLcz7fQRZr8voh8V10kGhABbNcHVk5wHgWwg==", + "dev": true, + "requires": { + "braces": "^3.0.1", + "picomatch": "^2.2.3" + } + }, + "mime-db": { + "version": "1.48.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.48.0.tgz", + "integrity": "sha512-FM3QwxV+TnZYQ2aRqhlKBMHxk10lTbMt3bBkMAp54ddrNeVSfcQYOOKuGuy3Ddrm38I04If834fOUSq1yzslJQ==", + "dev": true + }, + "mime-types": { + "version": "2.1.31", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.31.tgz", + "integrity": "sha512-XGZnNzm3QvgKxa8dpzyhFTHmpP3l5YNusmne07VUOXxou9CqUqYa/HBy124RqtVh/O2pECas/MOcsDgpilPOPg==", + "dev": true, + "requires": { + "mime-db": "1.48.0" + } + }, + "mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "dev": true + }, + "minimist": { + "version": "1.2.5", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz", + "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw==", + "dev": true + }, + "ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "neo-async": { + "version": "2.6.2", + "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", + "integrity": "sha512-Yd3UES5mWCSqR+qNT93S3UoYUkqAZ9lLg8a7g9rimsWmYGK8cVToA4/sF3RrshdyV3sAGMXVUmpMYOw+dLpOuw==", + "dev": true + }, + "node-releases": { + "version": "1.1.72", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-1.1.72.tgz", + "integrity": "sha512-LLUo+PpH3dU6XizX3iVoubUNheF/owjXCZZ5yACDxNnPtgFuludV1ZL3ayK1kVep42Rmm0+R9/Y60NQbZ2bifw==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "npm-run-path": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/npm-run-path/-/npm-run-path-4.0.1.tgz", + "integrity": "sha512-S48WzZW777zhNIrn7gxOlISNAqi9ZC/uQFnRdbeIHhZhCA6UqpkOT8T1G7BvfdgP4Er8gF4sUbaS0i7QvIfCWw==", + "dev": true, + "requires": { + "path-key": "^3.0.0" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.2.tgz", + "integrity": "sha512-ixT2L5THXsApyiUPYKmW+2EHpXXe5Ii3M+f4e+aJFAHao5amFRW6J0OO6c/LU8Be47utCx2GL89hxGB6XSmKuQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "define-properties": "^1.1.3", + "has-symbols": "^1.0.1", + "object-keys": "^1.1.1" + } + }, + "onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "dev": true, + "requires": { + "mimic-fn": "^2.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "dev": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "dev": true, + "requires": { + "p-limit": "^2.2.0" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "dev": true + }, + "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==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "path-type": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-type/-/path-type-4.0.0.tgz", + "integrity": "sha512-gDKb8aZMDeD/tZWs9P6+q0J9Mwkdl6xMV8TjnGP3qJVJ06bdMgkbBlLU8IdfOsIsFz2BW1rNVT3XuNEl8zPAvw==", + "dev": true + }, + "picomatch": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.0.tgz", + "integrity": "sha512-lY1Q/PiJGC2zOv/z391WOTD+Z02bCgsFfvxoXXf6h7kv9o+WmsmzYqrAwY63sNgOxE4xEdq0WyUnXfKeBrSvYw==", + "dev": true + }, + "pkg-dir": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz", + "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==", + "dev": true, + "requires": { + "find-up": "^4.0.0" + } + }, + "punycode": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", + "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==", + "dev": true + }, + "queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true + }, + "randombytes": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz", + "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==", + "dev": true, + "requires": { + "safe-buffer": "^5.1.0" + } + }, + "rechoir": { + "version": "0.7.0", + "resolved": "https://registry.npmjs.org/rechoir/-/rechoir-0.7.0.tgz", + "integrity": "sha512-ADsDEH2bvbjltXEP+hTIAmeFekTFK0V2BTxMkok6qILyAJEXV0AFfoWcAq4yfll5VdIMd/RVXq0lR+wQi5ZU3Q==", + "dev": true, + "requires": { + "resolve": "^1.9.0" + } + }, + "regenerate": { + "version": "1.4.2", + "resolved": "https://registry.npmjs.org/regenerate/-/regenerate-1.4.2.tgz", + "integrity": "sha512-zrceR/XhGYU/d/opr2EKO7aRHUeiBI8qjtfHqADTwZd6Szfy16la6kqD0MIUs5z5hx6AaKa+PixpPrR289+I0A==", + "dev": true + }, + "regenerate-unicode-properties": { + "version": "8.2.0", + "resolved": "https://registry.npmjs.org/regenerate-unicode-properties/-/regenerate-unicode-properties-8.2.0.tgz", + "integrity": "sha512-F9DjY1vKLo/tPePDycuH3dn9H1OTPIkVD9Kz4LODu+F2C75mgjAJ7x/gwy6ZcSNRAAkhNlJSOHRe8k3p+K9WhA==", + "dev": true, + "requires": { + "regenerate": "^1.4.0" + } + }, + "regenerator-runtime": { + "version": "0.13.7", + "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", + "dev": true + }, + "regenerator-transform": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/regenerator-transform/-/regenerator-transform-0.14.5.tgz", + "integrity": "sha512-eOf6vka5IO151Jfsw2NO9WpGX58W6wWmefK3I1zEGr0lOD0u8rwPaNqQL1aRxUaxLeKO3ArNh3VYg1KbaD+FFw==", + "dev": true, + "requires": { + "@babel/runtime": "^7.8.4" + } + }, + "regexpu-core": { + "version": "4.7.1", + "resolved": "https://registry.npmjs.org/regexpu-core/-/regexpu-core-4.7.1.tgz", + "integrity": "sha512-ywH2VUraA44DZQuRKzARmw6S66mr48pQVva4LBeRhcOltJ6hExvWly5ZjFLYo67xbIxb6W1q4bAGtgfEl20zfQ==", + "dev": true, + "requires": { + "regenerate": "^1.4.0", + "regenerate-unicode-properties": "^8.2.0", + "regjsgen": "^0.5.1", + "regjsparser": "^0.6.4", + "unicode-match-property-ecmascript": "^1.0.4", + "unicode-match-property-value-ecmascript": "^1.2.0" + } + }, + "regjsgen": { + "version": "0.5.2", + "resolved": "https://registry.npmjs.org/regjsgen/-/regjsgen-0.5.2.tgz", + "integrity": "sha512-OFFT3MfrH90xIW8OOSyUrk6QHD5E9JOTeGodiJeBS3J6IwlgzJMNE/1bZklWz5oTg+9dCMyEetclvCVXOPoN3A==", + "dev": true + }, + "regjsparser": { + "version": "0.6.9", + "resolved": "https://registry.npmjs.org/regjsparser/-/regjsparser-0.6.9.tgz", + "integrity": "sha512-ZqbNRz1SNjLAiYuwY0zoXW8Ne675IX5q+YHioAGbCw4X96Mjl2+dcX9B2ciaeyYjViDAfvIjFpQjJgLttTEERQ==", + "dev": true, + "requires": { + "jsesc": "~0.5.0" + }, + "dependencies": { + "jsesc": { + "version": "0.5.0", + "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-0.5.0.tgz", + "integrity": "sha1-597mbjXW/Bb3EP6R1c9p9w8IkR0=", + "dev": true + } + } + }, + "resolve": { + "version": "1.20.0", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.20.0.tgz", + "integrity": "sha512-wENBPt4ySzg4ybFQW2TT1zMQucPK95HSh/nq2CFTZVOGut2+pQvSsgtda4d26YrYcr067wjbmzOG8byDPBX63A==", + "dev": true, + "requires": { + "is-core-module": "^2.2.0", + "path-parse": "^1.0.6" + } + }, + "resolve-cwd": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/resolve-cwd/-/resolve-cwd-3.0.0.tgz", + "integrity": "sha512-OrZaX2Mb+rJCpH/6CpSqt9xFVpN++x01XnN2ie9g6P5/3xelLAkXWVADpdz1IHD/KFfEXyE6V0U01OQ3UO2rEg==", + "dev": true, + "requires": { + "resolve-from": "^5.0.0" + } + }, + "resolve-from": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-5.0.0.tgz", + "integrity": "sha512-qYg9KP24dD5qka9J47d0aVky0N+b4fTU89LN9iDnjB5waksiC49rvMB0PrUJQGoTmH50XPiqOvAjDfaijGxYZw==", + "dev": true + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "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==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "safe-buffer": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.1.2.tgz", + "integrity": "sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==", + "dev": true + }, + "schema-utils": { + "version": "2.7.1", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-2.7.1.tgz", + "integrity": "sha512-SHiNtMOUGWBQJwzISiVYKu82GiV4QYGePp3odlY1tuKO7gPtphAT5R/py0fA6xtbgLL/RvtJZnU9b8s0F1q0Xg==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.5", + "ajv": "^6.12.4", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "6.3.0", + "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz", + "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==", + "dev": true + }, + "serialize-javascript": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-5.0.1.tgz", + "integrity": "sha512-SaaNal9imEO737H2c05Og0/8LUXG7EnsZyMa8MzkmuHoELfT6txuj0cMqRj6zfPKnmQ1yasR4PCJc8x+M4JSPA==", + "dev": true, + "requires": { + "randombytes": "^2.1.0" + } + }, + "shallow-clone": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/shallow-clone/-/shallow-clone-3.0.1.tgz", + "integrity": "sha512-/6KqX+GVUdqPuPPd2LxDDxzX6CAbjJehAAOKlNpqqUpAqPM6HeL8f+o3a+JsyGjn2lv0WY8UsTgUJjU9Ok55NA==", + "dev": true, + "requires": { + "kind-of": "^6.0.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==", + "dev": true, + "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==", + "dev": true + }, + "signal-exit": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.3.tgz", + "integrity": "sha512-VUJ49FC8U1OxwZLxIbTTrDvLnf/6TDgxZcK8wxR8zs13xpx7xbG60ndBlhNrFi2EMuFRoeDoJO7wthSLq42EjA==", + "dev": true + }, + "slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true + }, + "source-list-map": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/source-list-map/-/source-list-map-2.0.1.tgz", + "integrity": "sha512-qnQ7gVMxGNxsiL4lEuJwe/To8UnK7fAnmbGEEH8RpLouuKbeEm0lhbQVFIrNSuB+G7tVrAlVsZgETT5nljf+Iw==", + "dev": true + }, + "source-map": { + "version": "0.5.7", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.5.7.tgz", + "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", + "dev": true + }, + "source-map-support": { + "version": "0.5.19", + "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", + "integrity": "sha512-Wonm7zOCIJzBGQdB+thsPar0kYuCIzYvxZwlBa87yi/Mdjv7Tip2cyVbLj5o0cFPN4EVkuTwb3GDDyUx2DGnGw==", + "dev": true, + "requires": { + "buffer-from": "^1.0.0", + "source-map": "^0.6.0" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "strip-final-newline": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/strip-final-newline/-/strip-final-newline-2.0.0.tgz", + "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + }, + "tapable": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/tapable/-/tapable-2.2.0.tgz", + "integrity": "sha512-FBk4IesMV1rBxX2tfiK8RAmogtWn53puLOQlvO8XuwlgxcYbP4mVPS9Ph4aeamSyyVjOl24aYWAuc8U5kCVwMw==", + "dev": true + }, + "terser": { + "version": "5.7.0", + "resolved": "https://registry.npmjs.org/terser/-/terser-5.7.0.tgz", + "integrity": "sha512-HP5/9hp2UaZt5fYkuhNBR8YyRcT8juw8+uFbAme53iN9hblvKnLUTKkmwJG6ocWpIKf8UK4DoeWG4ty0J6S6/g==", + "dev": true, + "requires": { + "commander": "^2.20.0", + "source-map": "~0.7.2", + "source-map-support": "~0.5.19" + }, + "dependencies": { + "source-map": { + "version": "0.7.3", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.7.3.tgz", + "integrity": "sha512-CkCj6giN3S+n9qrYiBTX5gystlENnRW5jZeNLHpe6aue+SrHcG5VYwujhW9s4dY31mEGsxBDrHR6oI69fTXsaQ==", + "dev": true + } + } + }, + "terser-webpack-plugin": { + "version": "5.1.3", + "resolved": "https://registry.npmjs.org/terser-webpack-plugin/-/terser-webpack-plugin-5.1.3.tgz", + "integrity": "sha512-cxGbMqr6+A2hrIB5ehFIF+F/iST5ZOxvOmy9zih9ySbP1C2oEWQSOUS+2SNBTjzx5xLKO4xnod9eywdfq1Nb9A==", + "dev": true, + "requires": { + "jest-worker": "^27.0.2", + "p-limit": "^3.1.0", + "schema-utils": "^3.0.0", + "serialize-javascript": "^5.0.1", + "source-map": "^0.6.1", + "terser": "^5.7.0" + }, + "dependencies": { + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", "dev": true, "requires": { - "string-width": "^1.0.2 || 2" - }, - "dependencies": { - "ansi-regex": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-3.0.0.tgz", - "integrity": "sha1-7QMXwyIGT3lGbAKWa922Bas32Zg=", - "dev": true - }, - "is-fullwidth-code-point": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-2.0.0.tgz", - "integrity": "sha1-o7MKXE8ZkYMWeqq5O+764937ZU8=", - "dev": true - }, - "string-width": { - "version": "2.1.1", - "resolved": "https://registry.npmjs.org/string-width/-/string-width-2.1.1.tgz", - "integrity": "sha512-nOqH59deCq9SRHlxq1Aw85Jnt4w6KvLKqWVik6oA9ZklXLNIOlqg4F2yrT1MVaTjAqvVwdfeZ7w7aCvJD7ugkw==", - "dev": true, - "requires": { - "is-fullwidth-code-point": "^2.0.0", - "strip-ansi": "^4.0.0" - } - }, - "strip-ansi": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-4.0.0.tgz", - "integrity": "sha1-qEeQIusaw2iocTibY1JixQXuNo8=", - "dev": true, - "requires": { - "ansi-regex": "^3.0.0" - } - } + "yocto-queue": "^0.1.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": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8=" - }, - "write": { - "version": "1.0.3", - "resolved": "https://registry.npmjs.org/write/-/write-1.0.3.tgz", - "integrity": "sha512-/lg70HAjtkUgWPVZhZcm+T4hkL8Zbtp1nFNOn3lRrxnlv50SRBv7cR7RqR+GMsd3hUXy9hWBo4CHTbFTcOYwig==", + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, "requires": { - "mkdirp": "^0.5.1" + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } }, - "yauzl": { - "version": "2.10.0", - "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz", - "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=", + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "to-fast-properties": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", + "integrity": "sha1-3F5pjL0HkmW8c+A3doGk5Og/YW4=", + "dev": true + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "unicode-canonical-property-names-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-canonical-property-names-ecmascript/-/unicode-canonical-property-names-ecmascript-1.0.4.tgz", + "integrity": "sha512-jDrNnXWHd4oHiTZnx/ZG7gtUTVp+gCcTTKr8L0HjlwphROEW3+Him+IpvC+xcJEFegapiMZyZe02CyuOnRmbnQ==", + "dev": true + }, + "unicode-match-property-ecmascript": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/unicode-match-property-ecmascript/-/unicode-match-property-ecmascript-1.0.4.tgz", + "integrity": "sha512-L4Qoh15vTfntsn4P1zqnHulG0LdXgjSO035fEpdtp6YxXhMT51Q6vgM5lYdG/5X3MjS+k/Y9Xw4SFCY9IkR0rg==", + "dev": true, + "requires": { + "unicode-canonical-property-names-ecmascript": "^1.0.4", + "unicode-property-aliases-ecmascript": "^1.0.4" + } + }, + "unicode-match-property-value-ecmascript": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/unicode-match-property-value-ecmascript/-/unicode-match-property-value-ecmascript-1.2.0.tgz", + "integrity": "sha512-wjuQHGQVofmSJv1uVISKLE5zO2rNGzM/KCYZch/QQvez7C1hUhBIuZ701fYXExuufJFMPhv2SyL8CyoIfMLbIQ==", + "dev": true + }, + "unicode-property-aliases-ecmascript": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/unicode-property-aliases-ecmascript/-/unicode-property-aliases-ecmascript-1.1.0.tgz", + "integrity": "sha512-PqSoPh/pWetQ2phoj5RLiaqIk4kCNwoV3CI+LfGmWLKI3rE3kl1h59XpX2BjgDrmbxD9ARtQobPGU1SguCYuQg==", + "dev": true + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "v8-compile-cache": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", + "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==", + "dev": true + }, + "watchpack": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/watchpack/-/watchpack-2.2.0.tgz", + "integrity": "sha512-up4YAn/XHgZHIxFBVCdlMiWDj6WaLKpwVeGQk2I5thdYxF/KmF0aaz6TfJZ/hfl1h/XlcDr7k1KH7ThDagpFaA==", + "dev": true, + "requires": { + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.1.2" + } + }, + "webpack": { + "version": "5.38.1", + "resolved": "https://registry.npmjs.org/webpack/-/webpack-5.38.1.tgz", + "integrity": "sha512-OqRmYD1OJbHZph6RUMD93GcCZy4Z4wC0ele4FXyYF0J6AxO1vOSuIlU1hkS/lDlR9CDYBz64MZRmdbdnFFoT2g==", + "dev": true, + "requires": { + "@types/eslint-scope": "^3.7.0", + "@types/estree": "^0.0.47", + "@webassemblyjs/ast": "1.11.0", + "@webassemblyjs/wasm-edit": "1.11.0", + "@webassemblyjs/wasm-parser": "1.11.0", + "acorn": "^8.2.1", + "browserslist": "^4.14.5", + "chrome-trace-event": "^1.0.2", + "enhanced-resolve": "^5.8.0", + "es-module-lexer": "^0.4.0", + "eslint-scope": "5.1.1", + "events": "^3.2.0", + "glob-to-regexp": "^0.4.1", + "graceful-fs": "^4.2.4", + "json-parse-better-errors": "^1.0.2", + "loader-runner": "^4.2.0", + "mime-types": "^2.1.27", + "neo-async": "^2.6.2", + "schema-utils": "^3.0.0", + "tapable": "^2.1.1", + "terser-webpack-plugin": "^5.1.1", + "watchpack": "^2.2.0", + "webpack-sources": "^2.3.0" + }, + "dependencies": { + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", "dev": true, "requires": { - "buffer-crc32": "~0.2.3", - "fd-slicer": "~1.1.0" - }, - "dependencies": { - "buffer-crc32": { - "version": "0.2.13", - "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz", - "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI=", - "dev": true - } + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" } } } + }, + "webpack-cli": { + "version": "4.7.0", + "resolved": "https://registry.npmjs.org/webpack-cli/-/webpack-cli-4.7.0.tgz", + "integrity": "sha512-7bKr9182/sGfjFm+xdZSwgQuFjgEcy0iCTIBxRUeteJ2Kr8/Wz0qNJX+jw60LU36jApt4nmMkep6+W5AKhok6g==", + "dev": true, + "requires": { + "@discoveryjs/json-ext": "^0.5.0", + "@webpack-cli/configtest": "^1.0.3", + "@webpack-cli/info": "^1.2.4", + "@webpack-cli/serve": "^1.4.0", + "colorette": "^1.2.1", + "commander": "^7.0.0", + "execa": "^5.0.0", + "fastest-levenshtein": "^1.0.12", + "import-local": "^3.0.2", + "interpret": "^2.2.0", + "rechoir": "^0.7.0", + "v8-compile-cache": "^2.2.0", + "webpack-merge": "^5.7.3" + }, + "dependencies": { + "commander": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-7.2.0.tgz", + "integrity": "sha512-QrWXB+ZQSVPmIWIhtEO9H+gwHaMGYiF5ChvoJ+K9ZGHG/sVsa6yiesAD1GC/x46sET00Xlwo1u49RVVVzvcSkw==", + "dev": true + } + } + }, + "webpack-merge": { + "version": "5.7.3", + "resolved": "https://registry.npmjs.org/webpack-merge/-/webpack-merge-5.7.3.tgz", + "integrity": "sha512-6/JUQv0ELQ1igjGDzHkXbVDRxkfA57Zw7PfiupdLFJYrgFqY5ZP8xxbpp2lU3EPwYx89ht5Z/aDkD40hFCm5AA==", + "dev": true, + "requires": { + "clone-deep": "^4.0.1", + "wildcard": "^2.0.0" + } + }, + "webpack-sources": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/webpack-sources/-/webpack-sources-2.3.0.tgz", + "integrity": "sha512-WyOdtwSvOML1kbgtXbTDnEW0jkJ7hZr/bDByIwszhWd/4XX1A3XMkrbFMsuH4+/MfLlZCUzlAdg4r7jaGKEIgQ==", + "dev": true, + "requires": { + "source-list-map": "^2.0.1", + "source-map": "^0.6.1" + }, + "dependencies": { + "source-map": { + "version": "0.6.1", + "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", + "integrity": "sha512-UjgapumWlbMhkBgzT7Ykc5YXUT46F0iKu8SGXq0bcwP5dz/h0Plj6enJqjz1Zbq2l5WaqYnrVbwWOWMyF3F47g==", + "dev": true + } + } + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "wildcard": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/wildcard/-/wildcard-2.0.0.tgz", + "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", + "dev": true + }, + "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==", + "dev": true } } } diff --git a/package.json b/package.json index 31225685..b616f6d7 100644 --- a/package.json +++ b/package.json @@ -4,7 +4,8 @@ "description": "Youtube App without ADs", "main": "index.js", "scripts": { - "package": "ares-package -n . -e node_modules -e .git", + "build": "webpack", + "package": "ares-package -n dist", "deploy": "ares-install youtube.leanback.v4_${npm_package_version}_all.ipk", "launch": "ares-launch youtube.leanback.v4" }, @@ -20,6 +21,12 @@ "homepage": "https://github.com/FriedChickenButt/youtube-webos#readme", "dependencies": {}, "devDependencies": { - "@webosose/ares-cli": "^2.1.0" + "@babel/core": "^7.14.3", + "@babel/preset-env": "^7.14.4", + "@webosose/ares-cli": "^2.1.0", + "babel-loader": "^8.2.2", + "copy-webpack-plugin": "^9.0.0", + "webpack": "^5.38.1", + "webpack-cli": "^4.7.0" } } diff --git a/index.html b/src/index.html similarity index 90% rename from index.html rename to src/index.html index 7ddec44e..43b8b3b4 100755 --- a/index.html +++ b/src/index.html @@ -4,4 +4,4 @@ - \ No newline at end of file + diff --git a/index.js b/src/index.js similarity index 100% rename from index.js rename to src/index.js diff --git a/webOSUserScripts/userScript.js b/src/userScript.js similarity index 100% rename from webOSUserScripts/userScript.js rename to src/userScript.js diff --git a/webpack.config.js b/webpack.config.js new file mode 100644 index 00000000..fe316408 --- /dev/null +++ b/webpack.config.js @@ -0,0 +1,42 @@ +const path = require('path'); +const CopyPlugin = require("copy-webpack-plugin"); + +module.exports = (env) => [ + { + mode: env.production ? 'production' : 'development', + + // Builds with devtool support (development) contain very big eval chunks, + // which seem to cause segfaults (at least) on nodeJS v0.12.2 used on webOS 3.x. + // This feature makes sense only when using recent enough chrome-based + // node inspector anyway. + devtool: false, + + entry: { + index: './src/index.js', + userScript: './src/userScript.js', + }, + output: { + path: path.resolve(__dirname, './dist'), + filename: ({ chunk: { name } }) => (name === 'userScript') ? 'webOSUserScripts/[name].js' : '[name].js', + }, + resolve: { + extensions: ['.ts', '.js'], + }, + module: { + rules: [ + { + test: /\.m?js$/, + use: 'babel-loader', + }, + ], + }, + plugins: [ + new CopyPlugin({ + patterns: [ + { context: 'assets', from: '**/*' }, + { context: 'src', from: 'index.html' }, + ] + }), + ], + }, +]; From 2b1ab03b9bad16b72dd17b72934d4705cd064115 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 11:06:59 +0200 Subject: [PATCH 03/24] WIP: sponsorblock integration --- babel.config.json | 2 +- package-lock.json | 18 +++- package.json | 7 +- src/adblock.js | 76 ++++++++++++++++ src/sponsorblock.js | 216 ++++++++++++++++++++++++++++++++++++++++++++ src/userScript.js | 80 +--------------- webpack.config.js | 2 + 7 files changed, 322 insertions(+), 79 deletions(-) create mode 100644 src/adblock.js create mode 100644 src/sponsorblock.js diff --git a/babel.config.json b/babel.config.json index 114908b9..b66ee5f7 100644 --- a/babel.config.json +++ b/babel.config.json @@ -3,7 +3,7 @@ ], "presets": [ [ - "@babel/env", + "@babel/preset-env", { "targets": { "chrome": "38" diff --git a/package-lock.json b/package-lock.json index 578f7f46..4c51f158 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5535,6 +5535,11 @@ } } }, + "core-js": { + "version": "3.13.1", + "resolved": "https://registry.npmjs.org/core-js/-/core-js-3.13.1.tgz", + "integrity": "sha512-JqveUc4igkqwStL2RTRn/EPFGBOfEZHxJl/8ej1mXJR75V3go2mFF4bmUYkEIT1rveHKnkUlcJX/c+f1TyIovQ==" + }, "core-js-compat": { "version": "3.13.1", "resolved": "https://registry.npmjs.org/core-js-compat/-/core-js-compat-3.13.1.tgz", @@ -6296,8 +6301,7 @@ "regenerator-runtime": { "version": "0.13.7", "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.7.tgz", - "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==", - "dev": true + "integrity": "sha512-a54FxoJDIr27pgf7IgeQGxmqUNYrcV338lf/6gH456HZ/PhX+5BcwHXG9ajESmwe6WRO0tAzRUrRmNONWgkrew==" }, "regenerator-transform": { "version": "0.14.5", @@ -6565,6 +6569,11 @@ } } }, + "tiny-sha256": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/tiny-sha256/-/tiny-sha256-1.0.2.tgz", + "integrity": "sha1-OyCnX3cJfc7Br1E/UYnCbsL1SZI=" + }, "to-fast-properties": { "version": "2.0.0", "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz", @@ -6734,6 +6743,11 @@ } } }, + "whatwg-fetch": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/whatwg-fetch/-/whatwg-fetch-3.6.2.tgz", + "integrity": "sha512-bJlen0FcuU/0EMLrdbJ7zOnW6ITZLrZMIarMUVmdKtsGvZna8vxKYaexICWPfZ8qwf9fzNq+UEIZrnSaApt6RA==" + }, "which": { "version": "2.0.2", "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", diff --git a/package.json b/package.json index b616f6d7..313c39db 100644 --- a/package.json +++ b/package.json @@ -19,7 +19,12 @@ "url": "https://github.com/FriedChickenButt/youtube-webos/issues" }, "homepage": "https://github.com/FriedChickenButt/youtube-webos#readme", - "dependencies": {}, + "dependencies": { + "core-js": "^3.13.1", + "regenerator-runtime": "^0.13.7", + "tiny-sha256": "^1.0.2", + "whatwg-fetch": "^3.6.2" + }, "devDependencies": { "@babel/core": "^7.14.3", "@babel/preset-env": "^7.14.4", diff --git a/src/adblock.js b/src/adblock.js new file mode 100644 index 00000000..f715a2e1 --- /dev/null +++ b/src/adblock.js @@ -0,0 +1,76 @@ +const YOUTUBE_REGEX = /^https?:\/\/(\w*.)?youtube.com/i; +const YOUTUBE_AD_REGEX = /(doubleclick\.net)|(adservice\.google\.)|(youtube\.com\/api\/stats\/ads)|(&ad_type=)|(&adurl=)|(-pagead-id.)|(doubleclick\.com)|(\/ad_status.)|(\/api\/ads\/)|(\/googleads)|(\/pagead\/gen_)|(\/pagead\/lvz?)|(\/pubads.)|(\/pubads_)|(\/securepubads)|(=adunit&)|(googlesyndication\.com)|(innovid\.com)|(youtube\.com\/pagead\/)|(google\.com\/pagead\/)|(flashtalking\.com)|(googleadservices\.com)|(s0\.2mdn\.net\/ads)|(www\.youtube\.com\/ptracking)|(www\.youtube\.com\/pagead)|(www\.youtube\.com\/get_midroll_)/; +const YOUTUBE_ANNOTATIONS_REGEX = /^https?:\/\/(\w*.)?youtube\.com\/annotations_invideo\?/; + +console.log("%cYT ADBlocker is loading...", "color: green;"); + +// Set these accoring to your preference +const settings = { + disable_ads: true, + disable_annotations: false, +}; + +function isRequestBlocked(requestType, url) { + console.log("[" + requestType + "] URL : " + url); + + if (settings.disable_ads && YOUTUBE_AD_REGEX.test(url)) { + console.log("%cBLOCK AD", "color: red;", url); + return true; + } + + if (settings.disable_annotations && YOUTUBE_ANNOTATIONS_REGEX.test(url)) { + console.log("%cBLOCK ANNOTATION", "color: red;", url); + return true; + } + + return false; +} + +/** + * Reference - https://gist.github.com/sergeimuller/a609a9df7d30e2625a177123797471e2 + * + * Wrapper over XHR. + */ +const origOpen = XMLHttpRequest.prototype.open; +XMLHttpRequest.prototype.open = function () { + const requestType = "XHR"; + const url = arguments[1]; + + if (isRequestBlocked(requestType, url)) { + throw "Blocked"; + } + + origOpen.apply(this, arguments); +}; + +/** + * Wrapper over Fetch. + */ +const origFetch = window.fetch; +fetch = function () { + const requestType = "FETCH"; + const url = arguments[0]; + + if (isRequestBlocked(requestType, url)) { + return; + } + return origFetch.apply(this, arguments); +}; + +/** + * This is a minimal reimplementation of the following uBlock Origin rule: + * https://github.com/uBlockOrigin/uAssets/blob/3497eebd440f4871830b9b45af0afc406c6eb593/filters/filters.txt#L116 + * + * This in turn calls the following snippet: + * https://github.com/gorhill/uBlock/blob/bfdc81e9e400f7b78b2abc97576c3d7bf3a11a0b/assets/resources/scriptlets.js#L365-L470 + * + * Seems like for now dropping just the adPlacements is enough for YouTube TV + */ +const origParse = JSON.parse; +JSON.parse = function () { + const r = origParse.apply(this, arguments); + if (r.adPlacements) { + r.adPlacements = []; + } + return r; +}; diff --git a/src/sponsorblock.js b/src/sponsorblock.js new file mode 100644 index 00000000..5241f363 --- /dev/null +++ b/src/sponsorblock.js @@ -0,0 +1,216 @@ +import sha256 from 'tiny-sha256'; + +// Copied from https://github.com/ajayyy/SponsorBlock/blob/9392d16617d2d48abb6125c00e2ff6042cb7bebe/src/config.ts#L179-L233 +const barTypes = { + "preview-chooseACategory": { + color: "#ffffff", + opacity: "0.7" + }, + "sponsor": { + color: "#00d400", + opacity: "0.7" + }, + "preview-sponsor": { + color: "#007800", + opacity: "0.7" + }, + "intro": { + color: "#00ffff", + opacity: "0.7" + }, + "preview-intro": { + color: "#008080", + opacity: "0.7" + }, + "outro": { + color: "#0202ed", + opacity: "0.7" + }, + "preview-outro": { + color: "#000070", + opacity: "0.7" + }, + "interaction": { + color: "#cc00ff", + opacity: "0.7" + }, + "preview-interaction": { + color: "#6c0087", + opacity: "0.7" + }, + "selfpromo": { + color: "#ffff00", + opacity: "0.7" + }, + "preview-selfpromo": { + color: "#bfbf35", + opacity: "0.7" + }, + "music_offtopic": { + color: "#ff9900", + opacity: "0.7" + }, + "preview-music_offtopic": { + color: "#a6634a", + opacity: "0.7" + } +}; + +class SponsorBlockHandler { + constructor(videoID, video) { + this.videoID = videoID; + this.video = video; + } + + async init() { + const videoHash = sha256(this.videoID).substring(0, 4); + const resp = await fetch(`https://sponsor.ajay.app/api/skipSegments/${videoHash}`) + const results = await resp.json(); + + const result = results.find((v) => v.videoID === this.videoID); + console.info('Got it:', result); + + if (!result || !result.segments || !result.segments.length) { + console.info('No segments found.'); + return; + } + + this.segments = result.segments; + + console.info('Video found, binding...'); + + this.scheduleSkipHandler = () => this.scheduleSkip(); + this.durationChangeHandler = () => this.buildOverlay(); + + this.video.addEventListener('progress', this.scheduleSkipHandler); + this.video.addEventListener('durationchange', this.durationChangeHandler); + + this.buildOverlay(); + } + + buildOverlay() { + if (this.segmentsoverlay) { + console.info('Overlay already built'); + return; + } + + if (!this.video.duration) { + console.info('No video duration yet'); + return; + } + + const videoDuration = this.video.duration; + + this.segmentsoverlay = document.createElement('div'); + this.segments.forEach(segment => { + const [start, end] = segment.segment; + const barType = barTypes[segment.category] || { color: 'blue', opacity: 0.7}; + const transform = `translateX(${start / videoDuration * 100.0}%) scaleX(${(end-start) / videoDuration})`; + const elm = document.createElement('div') + elm.classList.add('ytlr-progress-bar__played'); + elm.style['background'] = barType.color; + elm.style['opacity'] = barType.opacity; + elm.style['-webkit-transform'] = transform; + console.info('Generated element', elm, 'from', segment, transform); + this.segmentsoverlay.appendChild(elm); + }); + + this.observer = new MutationObserver((mutations, observer) => { + mutations.forEach((m) => { + if (m.removedNodes) { + m.removedNodes.forEach(n => { + if (n === this.segmentsoverlay) { + console.info('bringing back segments overlay'); + this.slider.appendChild(this.segmentsoverlay); + } + }); + } + }) + console.info(mutations); + }); + + this.sliderInterval = setInterval(() => { + this.slider = document.querySelector('.ytlr-progress-bar__slider'); + if (this.slider) { + clearInterval(this.sliderInterval); + this.sliderInterval = null; + this.observer.observe(this.slider, { + childList: true, + }); + this.slider.appendChild(this.segmentsoverlay); + } + }, 500); + } + + scheduleSkip() { + clearTimeout(this.nextSkip); + this.nextSkip = null; + + if (this.video.paused) { + console.info('Currently paused, ignoring...'); + return; + } + + console.info(this.video.currentTime, this.segments); + const nextSegments = this.segments.filter(seg => seg.segment[0] > this.video.currentTime - 0.1 && seg.segment[1] > this.video.currentTime - 0.1); + nextSegments.sort((s1, s2) => s1.segment[0] - s2.segment[0]); + + if (!nextSegments.length) { + console.info('No more segments'); + return; + } + + const [start, end] = nextSegments[0].segment; + console.info('Scheduling skip of', nextSegments[0], 'in', start - this.video.currentTime); + + this.nextSkip = setTimeout(() => { + console.info('Skipping', nextSegments[0]); + this.video.currentTime = end; + this.scheduleSkip(); + }, (start - this.video.currentTime) * 1000); + } + + destroy() { + console.info('destroy'); + if (this.nextSkip) { + clearTimeout(this.nextSkip); + this.nextSkip = null; + } + + if (this.sliderInterval) { + clearInterval(this.sliderInterval); + this.sliderInterval = null; + } + + if (this.observer) { + this.observer.disconnect(); + this.observer = null; + } + + if (this.segmentsoverlay) { + this.segmentsoverlay.destroy(); + } + + this.video.removeEventListener('progress', this.scheduleSkipHandler); + this.video.removeEventListener('durationchange', this.durationChangeHandler); + } +} + +let sponsorblock = null; + +window.addEventListener("hashchange", (evt) => { + const newURL = new URL(location.hash.substring(1), location.href); + const videoID = newURL.searchParams.get('v'); + const needsReload = videoID && (!sponsorblock || sponsorblock.videoID != videoID); + + if (needsReload) { + if (sponsorblock) { + sponsorblock.destroy(); + sponsorblock = null; + } + + const video = document.querySelector('video'); + sponsorblock = new SponsorBlockHandler(videoID, video); + sponsorblock.init(); + } +}, false); diff --git a/src/userScript.js b/src/userScript.js index f715a2e1..030e765e 100644 --- a/src/userScript.js +++ b/src/userScript.js @@ -1,76 +1,6 @@ -const YOUTUBE_REGEX = /^https?:\/\/(\w*.)?youtube.com/i; -const YOUTUBE_AD_REGEX = /(doubleclick\.net)|(adservice\.google\.)|(youtube\.com\/api\/stats\/ads)|(&ad_type=)|(&adurl=)|(-pagead-id.)|(doubleclick\.com)|(\/ad_status.)|(\/api\/ads\/)|(\/googleads)|(\/pagead\/gen_)|(\/pagead\/lvz?)|(\/pubads.)|(\/pubads_)|(\/securepubads)|(=adunit&)|(googlesyndication\.com)|(innovid\.com)|(youtube\.com\/pagead\/)|(google\.com\/pagead\/)|(flashtalking\.com)|(googleadservices\.com)|(s0\.2mdn\.net\/ads)|(www\.youtube\.com\/ptracking)|(www\.youtube\.com\/pagead)|(www\.youtube\.com\/get_midroll_)/; -const YOUTUBE_ANNOTATIONS_REGEX = /^https?:\/\/(\w*.)?youtube\.com\/annotations_invideo\?/; +import 'whatwg-fetch'; +import 'core-js/stable'; +import 'regenerator-runtime/runtime'; -console.log("%cYT ADBlocker is loading...", "color: green;"); - -// Set these accoring to your preference -const settings = { - disable_ads: true, - disable_annotations: false, -}; - -function isRequestBlocked(requestType, url) { - console.log("[" + requestType + "] URL : " + url); - - if (settings.disable_ads && YOUTUBE_AD_REGEX.test(url)) { - console.log("%cBLOCK AD", "color: red;", url); - return true; - } - - if (settings.disable_annotations && YOUTUBE_ANNOTATIONS_REGEX.test(url)) { - console.log("%cBLOCK ANNOTATION", "color: red;", url); - return true; - } - - return false; -} - -/** - * Reference - https://gist.github.com/sergeimuller/a609a9df7d30e2625a177123797471e2 - * - * Wrapper over XHR. - */ -const origOpen = XMLHttpRequest.prototype.open; -XMLHttpRequest.prototype.open = function () { - const requestType = "XHR"; - const url = arguments[1]; - - if (isRequestBlocked(requestType, url)) { - throw "Blocked"; - } - - origOpen.apply(this, arguments); -}; - -/** - * Wrapper over Fetch. - */ -const origFetch = window.fetch; -fetch = function () { - const requestType = "FETCH"; - const url = arguments[0]; - - if (isRequestBlocked(requestType, url)) { - return; - } - return origFetch.apply(this, arguments); -}; - -/** - * This is a minimal reimplementation of the following uBlock Origin rule: - * https://github.com/uBlockOrigin/uAssets/blob/3497eebd440f4871830b9b45af0afc406c6eb593/filters/filters.txt#L116 - * - * This in turn calls the following snippet: - * https://github.com/gorhill/uBlock/blob/bfdc81e9e400f7b78b2abc97576c3d7bf3a11a0b/assets/resources/scriptlets.js#L365-L470 - * - * Seems like for now dropping just the adPlacements is enough for YouTube TV - */ -const origParse = JSON.parse; -JSON.parse = function () { - const r = origParse.apply(this, arguments); - if (r.adPlacements) { - r.adPlacements = []; - } - return r; -}; +import './adblock.js'; +import './sponsorblock.js'; diff --git a/webpack.config.js b/webpack.config.js index fe316408..f730e023 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -5,6 +5,8 @@ module.exports = (env) => [ { mode: env.production ? 'production' : 'development', + target: 'es5', + // Builds with devtool support (development) contain very big eval chunks, // which seem to cause segfaults (at least) on nodeJS v0.12.2 used on webOS 3.x. // This feature makes sense only when using recent enough chrome-based From 95aa3688888522a2f1db51bae8a4ae7c4e307b08 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 11:18:32 +0200 Subject: [PATCH 04/24] sponsorblock: explain nextSegments lookback hack --- src/sponsorblock.js | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 5241f363..8d53c22b 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -152,6 +152,10 @@ class SponsorBlockHandler { } console.info(this.video.currentTime, this.segments); + + // Sometimes progress event (that calls scheduleSkip) gets fired right before + // already scheduled skip routine below. Let's just look back a little bit + // and, in worst case, perform a skip at negative interval (immediately)... const nextSegments = this.segments.filter(seg => seg.segment[0] > this.video.currentTime - 0.1 && seg.segment[1] > this.video.currentTime - 0.1); nextSegments.sort((s1, s2) => s1.segment[0] - s2.segment[0]); From 39577d41320218afcfc0bc60cc4abcd5ddb32d78 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 11:24:53 +0200 Subject: [PATCH 05/24] sponsorblock: use proper event for skip scheduling... --- src/sponsorblock.js | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 8d53c22b..7c88014f 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -82,7 +82,7 @@ class SponsorBlockHandler { this.scheduleSkipHandler = () => this.scheduleSkip(); this.durationChangeHandler = () => this.buildOverlay(); - this.video.addEventListener('progress', this.scheduleSkipHandler); + this.video.addEventListener('timeupdate', this.scheduleSkipHandler); this.video.addEventListener('durationchange', this.durationChangeHandler); this.buildOverlay(); @@ -153,7 +153,7 @@ class SponsorBlockHandler { console.info(this.video.currentTime, this.segments); - // Sometimes progress event (that calls scheduleSkip) gets fired right before + // Sometimes timeupdate event (that calls scheduleSkip) gets fired right before // already scheduled skip routine below. Let's just look back a little bit // and, in worst case, perform a skip at negative interval (immediately)... const nextSegments = this.segments.filter(seg => seg.segment[0] > this.video.currentTime - 0.1 && seg.segment[1] > this.video.currentTime - 0.1); From fcb1becc815d67b07036e8929bb79be0abe0e5f9 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 11:32:12 +0200 Subject: [PATCH 06/24] sponsorblock: fix crashes on video change --- src/sponsorblock.js | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 7c88014f..8e7150f0 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -192,7 +192,8 @@ class SponsorBlockHandler { } if (this.segmentsoverlay) { - this.segmentsoverlay.destroy(); + this.segmentsoverlay.remove(); + this.segmentsoverlay = null; } this.video.removeEventListener('progress', this.scheduleSkipHandler); @@ -209,7 +210,11 @@ window.addEventListener("hashchange", (evt) => { if (needsReload) { if (sponsorblock) { - sponsorblock.destroy(); + try { + sponsorblock.destroy(); + } catch (err) { + console.warn('sponsorblock.destroy() failed!', err); + } sponsorblock = null; } From 1372d85079ef26e2423900f4f50599fa548315f7 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 11:34:26 +0200 Subject: [PATCH 07/24] Update README --- README.md | 15 ++++++++++----- 1 file changed, 10 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index bdc182e7..7eec844e 100644 --- a/README.md +++ b/README.md @@ -8,17 +8,17 @@ Youtube App without ADs ## Building * Clone the repository -``` +```sh git clone https://github.com/FriedChickenButt/youtube-webos.git ``` * Enter the folder and build the App, this will generate a `*.ipk` file. -``` +```sh cd youtube-webos -# Optionally, if you haven't installed full SDK: +# Install dependencies (need to do this only when updating local repository / package.json is changed) npm install -npm run package +npm run build && npm run package ``` ## Installation @@ -28,6 +28,11 @@ npm run deploy ## Launching * The app will be available in the TV's app list or launch it using ares-cli. -``` +```sh npm run launch ``` + +To jump immediately into some specific video use: +```sh +npm run launch -- -p '{"contentTarget":"v=F8PGWLvn1mQ"}' +``` From fe9a173c2c7cef8f590a2264b027017b30abd2cf Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 17:31:57 +0200 Subject: [PATCH 08/24] sponsorblock: fix skip on start, fetch all categories --- src/sponsorblock.js | 13 +++++++++++-- 1 file changed, 11 insertions(+), 2 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 8e7150f0..24ca95c8 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -64,7 +64,8 @@ class SponsorBlockHandler { async init() { const videoHash = sha256(this.videoID).substring(0, 4); - const resp = await fetch(`https://sponsor.ajay.app/api/skipSegments/${videoHash}`) + const categories = ["sponsor", "intro", "outro", "interaction", "selfpromo", "music_offtopic"]; + const resp = await fetch(`https://sponsor.ajay.app/api/skipSegments/${videoHash}?categories=${encodeURIComponent(JSON.stringify(categories))}`); const results = await resp.json(); const result = results.find((v) => v.videoID === this.videoID); @@ -82,6 +83,8 @@ class SponsorBlockHandler { this.scheduleSkipHandler = () => this.scheduleSkip(); this.durationChangeHandler = () => this.buildOverlay(); + this.video.addEventListener('play', this.scheduleSkipHandler); + this.video.addEventListener('pause', this.scheduleSkipHandler); this.video.addEventListener('timeupdate', this.scheduleSkipHandler); this.video.addEventListener('durationchange', this.durationChangeHandler); @@ -126,7 +129,6 @@ class SponsorBlockHandler { }); } }) - console.info(mutations); }); this.sliderInterval = setInterval(() => { @@ -168,6 +170,11 @@ class SponsorBlockHandler { console.info('Scheduling skip of', nextSegments[0], 'in', start - this.video.currentTime); this.nextSkip = setTimeout(() => { + if (this.video.paused) { + console.info('Currently paused, ignoring...'); + return; + } + console.info('Skipping', nextSegments[0]); this.video.currentTime = end; this.scheduleSkip(); @@ -196,6 +203,8 @@ class SponsorBlockHandler { this.segmentsoverlay = null; } + this.video.removeEventListener('play', this.scheduleSkipHandler); + this.video.removeEventListener('pause', this.scheduleSkipHandler); this.video.removeEventListener('progress', this.scheduleSkipHandler); this.video.removeEventListener('durationchange', this.durationChangeHandler); } From 9ea80f9b506e4d9f358031b078c256fcdd6aefe1 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 23:14:22 +0200 Subject: [PATCH 09/24] sponsorblock: try fixing some retaining skips when playing a video queue --- src/sponsorblock.js | 23 +++++++++++++++-------- 1 file changed, 15 insertions(+), 8 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 24ca95c8..f0080390 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -60,6 +60,7 @@ class SponsorBlockHandler { constructor(videoID, video) { this.videoID = videoID; this.video = video; + this.active = true; } async init() { @@ -148,12 +149,15 @@ class SponsorBlockHandler { clearTimeout(this.nextSkip); this.nextSkip = null; - if (this.video.paused) { - console.info('Currently paused, ignoring...'); + if (!this.active) { + console.info(this.videoID, 'No longer active, ignoring...'); return; } - console.info(this.video.currentTime, this.segments); + if (this.video.paused) { + console.info(this.videoID, 'Currently paused, ignoring...'); + return; + } // Sometimes timeupdate event (that calls scheduleSkip) gets fired right before // already scheduled skip routine below. Let's just look back a little bit @@ -162,27 +166,30 @@ class SponsorBlockHandler { nextSegments.sort((s1, s2) => s1.segment[0] - s2.segment[0]); if (!nextSegments.length) { - console.info('No more segments'); + console.info(this.videoID, 'No more segments'); return; } const [start, end] = nextSegments[0].segment; - console.info('Scheduling skip of', nextSegments[0], 'in', start - this.video.currentTime); + console.info(this.videoID, 'Scheduling skip of', nextSegments[0], 'in', start - this.video.currentTime); this.nextSkip = setTimeout(() => { if (this.video.paused) { - console.info('Currently paused, ignoring...'); + console.info(this.videoID, 'Currently paused, ignoring...'); return; } - console.info('Skipping', nextSegments[0]); + console.info(this.videoID, 'Skipping', nextSegments[0]); this.video.currentTime = end; this.scheduleSkip(); }, (start - this.video.currentTime) * 1000); } destroy() { - console.info('destroy'); + console.info(this.videoID, 'Destroying'); + + this.active = false; + if (this.nextSkip) { clearTimeout(this.nextSkip); this.nextSkip = null; From 3a3040bfaefe92fe65ae001d2d60ab17dfa01771 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 23:49:19 +0200 Subject: [PATCH 10/24] sponsorblock: cleanup timeupdate handler properly --- src/sponsorblock.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index f0080390..7baa2943 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -70,16 +70,16 @@ class SponsorBlockHandler { const results = await resp.json(); const result = results.find((v) => v.videoID === this.videoID); - console.info('Got it:', result); + console.info(this.videoID, 'Got it:', result); if (!result || !result.segments || !result.segments.length) { - console.info('No segments found.'); + console.info(this.videoID, 'No segments found.'); return; } this.segments = result.segments; - console.info('Video found, binding...'); + console.info(this.videoID, 'Video found, binding...'); this.scheduleSkipHandler = () => this.scheduleSkip(); this.durationChangeHandler = () => this.buildOverlay(); @@ -212,7 +212,7 @@ class SponsorBlockHandler { this.video.removeEventListener('play', this.scheduleSkipHandler); this.video.removeEventListener('pause', this.scheduleSkipHandler); - this.video.removeEventListener('progress', this.scheduleSkipHandler); + this.video.removeEventListener('timeupdate', this.scheduleSkipHandler); this.video.removeEventListener('durationchange', this.durationChangeHandler); } } From ac98795d1adb7f1955639d948307f34c594ccb78 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 5 Jun 2021 23:49:43 +0200 Subject: [PATCH 11/24] sponsorblock: use window.sponsorblock global variable for state handling --- src/sponsorblock.js | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 7baa2943..826d345e 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -217,25 +217,33 @@ class SponsorBlockHandler { } } -let sponsorblock = null; +// When this global variable was declared using let and two consecutive hashchange +// events were fired (due to bubbling? not sure...) the second call handled below +// would not see the value change from first call, and that would cause multiple +// SponsorBlockHandler initializations... This has been noticed on Chromium 38. +// This either reveals some bug in chromium/webpack/babel scope handling, or +// shows my lack of understanding of javascript. (or both) +window.sponsorblock = null; window.addEventListener("hashchange", (evt) => { const newURL = new URL(location.hash.substring(1), location.href); const videoID = newURL.searchParams.get('v'); - const needsReload = videoID && (!sponsorblock || sponsorblock.videoID != videoID); + const needsReload = videoID && (!window.sponsorblock || window.sponsorblock.videoID != videoID); + + console.info('hashchange', videoID, window.sponsorblock, window.sponsorblock ? window.sponsorblock.videoID : null, needsReload); if (needsReload) { - if (sponsorblock) { + if (window.sponsorblock) { try { - sponsorblock.destroy(); + window.sponsorblock.destroy(); } catch (err) { - console.warn('sponsorblock.destroy() failed!', err); + console.warn('window.sponsorblock.destroy() failed!', err); } - sponsorblock = null; + window.sponsorblock = null; } const video = document.querySelector('video'); - sponsorblock = new SponsorBlockHandler(videoID, video); - sponsorblock.init(); + window.sponsorblock = new SponsorBlockHandler(videoID, video); + window.sponsorblock.init(); } }, false); From f908a1fa3091801ab40bf659b68b492926738b0e Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 00:20:10 +0200 Subject: [PATCH 12/24] index: fix multiple userscripts loading This is one very weird bug. Before this change youtube.com/tv has always been started as youtube.com/tv?undefined, unless a video ID has been passed via launch params. This lead to the app doing some weird navigation, which in turn seemed to leave our userscript in semi-unloaded state - event handlers bound on `document` (eg. keydown) were properly cleaned up, but handlers on `window` (eg. hashchange) were kept, while another instance of a script was loaded. This likely lead to very weird global/script-local variable behaviour noticed before. This mostly seems like a bug in userscript handling on webappmgr side, that just happened to get triggered by youtube navigating off an invalid URL we generated due to bug in our codebase, but definitely needs to be researched... --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index 85b3a5a9..1a43e1c5 100644 --- a/src/index.js +++ b/src/index.js @@ -10,7 +10,7 @@ function extractLaunchUrlParams(launchParameters) { } function concatenateUrlAndGetParams(ytUrl, path) { - if (path === null) { + if (!path) { return ytUrl; } else { return ytUrl + "?" + path; From 2e4843b4dee6179648714111d62e6d0ed3e79ccd Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 10:22:49 +0200 Subject: [PATCH 13/24] src: initial UI view mockup --- package-lock.json | 192 +++ package.json | 2 + src/domrect-polyfill.js | 104 ++ src/spatial-navigation-polyfill.js | 1756 ++++++++++++++++++++++++++++ src/ui.css | 34 + src/ui.js | 68 ++ src/userScript.js | 2 + webpack.config.js | 4 + 8 files changed, 2162 insertions(+) create mode 100644 src/domrect-polyfill.js create mode 100644 src/spatial-navigation-polyfill.js create mode 100644 src/ui.css create mode 100644 src/ui.js diff --git a/package-lock.json b/package-lock.json index 4c51f158..5e00f560 100644 --- a/package-lock.json +++ b/package-lock.json @@ -5569,6 +5569,63 @@ "which": "^2.0.1" } }, + "css-loader": { + "version": "5.2.6", + "resolved": "https://registry.npmjs.org/css-loader/-/css-loader-5.2.6.tgz", + "integrity": "sha512-0wyN5vXMQZu6BvjbrPdUJvkCzGEO24HC7IS7nW4llc6BBFC+zwR9CKtYGv63Puzsg10L/o12inMY5/2ByzfD6w==", + "dev": true, + "requires": { + "icss-utils": "^5.1.0", + "loader-utils": "^2.0.0", + "postcss": "^8.2.15", + "postcss-modules-extract-imports": "^3.0.0", + "postcss-modules-local-by-default": "^4.0.0", + "postcss-modules-scope": "^3.0.0", + "postcss-modules-values": "^4.0.0", + "postcss-value-parser": "^4.1.0", + "schema-utils": "^3.0.0", + "semver": "^7.3.5" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + }, + "semver": { + "version": "7.3.5", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.5.tgz", + "integrity": "sha512-PoeGJYh8HK4BTO/a9Tf6ZG3veo/A7ZVsYrSA6J8ny9nb3B1VrpkuN+z9OE5wfE5p6H4LchYZsegiQgbJD94ZFQ==", + "dev": true, + "requires": { + "lru-cache": "^6.0.0" + } + } + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, "debug": { "version": "4.3.1", "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz", @@ -5883,6 +5940,12 @@ "integrity": "sha512-B4FFZ6q/T2jhhksgkbEW3HBvWIfDW85snkQgawt07S7J5QXTk6BkNV+0yAeZrM5QpMAdYlocGoljn0sJ/WQkFw==", "dev": true }, + "icss-utils": { + "version": "5.1.0", + "resolved": "https://registry.npmjs.org/icss-utils/-/icss-utils-5.1.0.tgz", + "integrity": "sha512-soFhflCVWLfRNOPU3iv5Z9VUdT44xFRbzjLsEzSr5AQmgqPMTHdU3PMT1Cf1ssx8fLNJDA1juftYl+PUcv3MqA==", + "dev": true + }, "ignore": { "version": "5.1.8", "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.1.8.tgz", @@ -6072,6 +6135,15 @@ "integrity": "sha1-gteb/zCmfEAF/9XiUVMArZyk168=", "dev": true }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, "make-dir": { "version": "3.1.0", "resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz", @@ -6136,6 +6208,12 @@ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", "dev": true }, + "nanoid": { + "version": "3.1.23", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.1.23.tgz", + "integrity": "sha512-FiB0kzdP0FFVGDKlRLEQ1BgDzU87dy5NnzjeW9YZNt+/c3+q82EQDUwniSAUxp/F0gFNI1ZhKU1FqYsMuqZVnw==", + "dev": true + }, "neo-async": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/neo-async/-/neo-async-2.6.2.tgz", @@ -6253,6 +6331,68 @@ "find-up": "^4.0.0" } }, + "postcss": { + "version": "8.3.0", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.3.0.tgz", + "integrity": "sha512-+ogXpdAjWGa+fdYY5BQ96V/6tAo+TdSSIMP5huJBIygdWwKtVoB5JWZ7yUd4xZ8r+8Kvvx4nyg/PQ071H4UtcQ==", + "dev": true, + "requires": { + "colorette": "^1.2.2", + "nanoid": "^3.1.23", + "source-map-js": "^0.6.2" + } + }, + "postcss-modules-extract-imports": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-extract-imports/-/postcss-modules-extract-imports-3.0.0.tgz", + "integrity": "sha512-bdHleFnP3kZ4NYDhuGlVK+CMrQ/pqUm8bx/oGL93K6gVwiclvX5x0n76fYMKuIGKzlABOy13zsvqjb0f92TEXw==", + "dev": true + }, + "postcss-modules-local-by-default": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-local-by-default/-/postcss-modules-local-by-default-4.0.0.tgz", + "integrity": "sha512-sT7ihtmGSF9yhm6ggikHdV0hlziDTX7oFoXtuVWeDd3hHObNkcHRo9V3yg7vCAY7cONyxJC/XXCmmiHHcvX7bQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0", + "postcss-selector-parser": "^6.0.2", + "postcss-value-parser": "^4.1.0" + } + }, + "postcss-modules-scope": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-scope/-/postcss-modules-scope-3.0.0.tgz", + "integrity": "sha512-hncihwFA2yPath8oZ15PZqvWGkWf+XUfQgUGamS4LqoP1anQLOsOJw0vr7J7IwLpoY9fatA2qiGUGmuZL0Iqlg==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.4" + } + }, + "postcss-modules-values": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/postcss-modules-values/-/postcss-modules-values-4.0.0.tgz", + "integrity": "sha512-RDxHkAiEGI78gS2ofyvCsu7iycRv7oqw5xMWn9iMoR0N/7mf9D50ecQqUo5BZ9Zh2vH4bCUR/ktCqbB9m8vJjQ==", + "dev": true, + "requires": { + "icss-utils": "^5.0.0" + } + }, + "postcss-selector-parser": { + "version": "6.0.6", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.6.tgz", + "integrity": "sha512-9LXrvaaX3+mcv5xkg5kFwqSzSH1JIObIx51PrndZwlmznwXRfxMddDvo9gve3gVR8ZTKgoFDdWkbRFmEhT4PMg==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.1.0.tgz", + "integrity": "sha512-97DXOFbQJhk71ne5/Mt6cOu6yxsSfM0QGQyl0L25Gca4yGWEGJaig7l7gbCX623VqTBNGLRLaVUCnNkcedlRSQ==", + "dev": true + }, "punycode": { "version": "2.1.1", "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz", @@ -6469,6 +6609,12 @@ "integrity": "sha1-igOdLRAh0i0eoUyA2OpGi6LvP8w=", "dev": true }, + "source-map-js": { + "version": "0.6.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-0.6.2.tgz", + "integrity": "sha512-/3GptzWzu0+0MBQFrDKzw/DvvMTUORvgY6k6jd/VS6iCR4RDTKWH6v6WPwQoUO8667uQEf9Oe38DxAYWY5F/Ug==", + "dev": true + }, "source-map-support": { "version": "0.5.19", "resolved": "https://registry.npmjs.org/source-map-support/-/source-map-support-0.5.19.tgz", @@ -6493,6 +6639,40 @@ "integrity": "sha512-BrpvfNAE3dcvq7ll3xVumzjKjZQ5tI1sEUIKr3Uoks0XUl45St3FlatVqef9prk4jRDzhW6WZg+3bk93y6pLjA==", "dev": true }, + "style-loader": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/style-loader/-/style-loader-2.0.0.tgz", + "integrity": "sha512-Z0gYUJmzZ6ZdRUqpg1r8GsaFKypE+3xAzuFeMuoHgjc9KZv3wMyCRjQIWEbhoFSq7+7yoHXySDJyyWQaPajeiQ==", + "dev": true, + "requires": { + "loader-utils": "^2.0.0", + "schema-utils": "^3.0.0" + }, + "dependencies": { + "loader-utils": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/loader-utils/-/loader-utils-2.0.0.tgz", + "integrity": "sha512-rP4F0h2RaWSvPEkD7BLDFQnvSf+nK+wr3ESUjNTyAGobqrijmW92zc+SO6d4p4B1wh7+B/Jg1mkQe5NYUEHtHQ==", + "dev": true, + "requires": { + "big.js": "^5.2.2", + "emojis-list": "^3.0.0", + "json5": "^2.1.2" + } + }, + "schema-utils": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/schema-utils/-/schema-utils-3.0.0.tgz", + "integrity": "sha512-6D82/xSzO094ajanoOSbe4YvXWMfn2A//8Y1+MUqFAJul5Bs+yn36xbK9OtNDcRVSBJ9jjeoXftM6CfztsjOAA==", + "dev": true, + "requires": { + "@types/json-schema": "^7.0.6", + "ajv": "^6.12.5", + "ajv-keywords": "^3.5.2" + } + } + } + }, "supports-color": { "version": "5.5.0", "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", @@ -6626,6 +6806,12 @@ "punycode": "^2.1.0" } }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8=", + "dev": true + }, "v8-compile-cache": { "version": "2.3.0", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz", @@ -6763,6 +6949,12 @@ "integrity": "sha512-JcKqAHLPxcdb9KM49dufGXn2x3ssnfjbcaQdLlfZsL9rH9wgDQjUtDxbo8NE0F6SFvydeu1VhZe7hZuHsB2/pw==", "dev": true }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, "yocto-queue": { "version": "0.1.0", "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", diff --git a/package.json b/package.json index 313c39db..6eb39a73 100644 --- a/package.json +++ b/package.json @@ -31,6 +31,8 @@ "@webosose/ares-cli": "^2.1.0", "babel-loader": "^8.2.2", "copy-webpack-plugin": "^9.0.0", + "css-loader": "^5.2.6", + "style-loader": "^2.0.0", "webpack": "^5.38.1", "webpack-cli": "^4.7.0" } diff --git a/src/domrect-polyfill.js b/src/domrect-polyfill.js new file mode 100644 index 00000000..7d2f8b73 --- /dev/null +++ b/src/domrect-polyfill.js @@ -0,0 +1,104 @@ +// +// https://raw.githubusercontent.com/Financial-Times/polyfill-library/c25c30e4463bef60fba1213ecb697f3e3f253d7b/polyfills/DOMRect/polyfill.js +// License: MIT +// + +(function (global) { + function number(v) { + return v === undefined ? 0 : Number(v); + } + + function different(u, v) { + return u !== v && !(isNaN(u) && isNaN(v)); + } + + function DOMRect(xArg, yArg, wArg, hArg) { + var x, y, width, height, left, right, top, bottom; + + x = number(xArg); + y = number(yArg); + width = number(wArg); + height = number(hArg); + + Object.defineProperties(this, { + x: { + get: function () { return x; }, + set: function (newX) { + if (different(x, newX)) { + x = newX; + left = right = undefined; + } + }, + enumerable: true + }, + y: { + get: function () { return y; }, + set: function (newY) { + if (different(y, newY)) { + y = newY; + top = bottom = undefined; + } + }, + enumerable: true + }, + width: { + get: function () { return width; }, + set: function (newWidth) { + if (different(width, newWidth)) { + width = newWidth; + left = right = undefined; + } + }, + enumerable: true + }, + height: { + get: function () { return height; }, + set: function (newHeight) { + if (different(height, newHeight)) { + height = newHeight; + top = bottom = undefined; + } + }, + enumerable: true + }, + left: { + get: function () { + if (left === undefined) { + left = x + Math.min(0, width); + } + return left; + }, + enumerable: true + }, + right: { + get: function () { + if (right === undefined) { + right = x + Math.max(0, width); + } + return right; + }, + enumerable: true + }, + top: { + get: function () { + if (top === undefined) { + top = y + Math.min(0, height); + } + return top; + }, + enumerable: true + }, + bottom: { + get: function () { + if (bottom === undefined) { + bottom = y + Math.max(0, height); + } + return bottom; + }, + enumerable: true + } + }); + } + + global.DOMRect = DOMRect; +}(self)); diff --git a/src/spatial-navigation-polyfill.js b/src/spatial-navigation-polyfill.js new file mode 100644 index 00000000..00244ef1 --- /dev/null +++ b/src/spatial-navigation-polyfill.js @@ -0,0 +1,1756 @@ +// +// https://raw.githubusercontent.com/WICG/spatial-navigation/183f0146b6741007e46fa64ab0950447defdf8af/polyfill/spatial-navigation-polyfill.js +// License: MIT +// + +/* Spatial Navigation Polyfill + * + * It follows W3C official specification + * https://drafts.csswg.org/css-nav-1/ + * + * Copyright (c) 2018-2019 LG Electronics Inc. + * https://github.com/WICG/spatial-navigation/polyfill + * + * Licensed under the MIT license (MIT) + */ + +(function () { + + // The polyfill must not be executed, if it's already enabled via browser engine or browser extensions. + if ('navigate' in window) { + return; + } + + const ARROW_KEY_CODE = {37: 'left', 38: 'up', 39: 'right', 40: 'down'}; + const TAB_KEY_CODE = 9; + let mapOfBoundRect = null; + let startingPoint = null; // Saves spatial navigation starting point + let savedSearchOrigin = {element: null, rect: null}; // Saves previous search origin + let searchOriginRect = null; // Rect of current search origin + + /** + * Initiate the spatial navigation features of the polyfill. + * @function initiateSpatialNavigation + */ + function initiateSpatialNavigation() { + /* + * Bind the standards APIs to be exposed to the window object for authors + */ + window.navigate = navigate; + window.Element.prototype.spatialNavigationSearch = spatialNavigationSearch; + window.Element.prototype.focusableAreas = focusableAreas; + window.Element.prototype.getSpatialNavigationContainer = getSpatialNavigationContainer; + + /* + * CSS.registerProperty() from the Properties and Values API + * Reference: https://drafts.css-houdini.org/css-properties-values-api/#the-registerproperty-function + */ + if (window.CSS && CSS.registerProperty) { + if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-contain') === '') { + CSS.registerProperty({ + name: '--spatial-navigation-contain', + syntax: 'auto | contain', + inherits: false, + initialValue: 'auto' + }); + } + + if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-action') === '') { + CSS.registerProperty({ + name: '--spatial-navigation-action', + syntax: 'auto | focus | scroll', + inherits: false, + initialValue: 'auto' + }); + } + + if (window.getComputedStyle(document.documentElement).getPropertyValue('--spatial-navigation-function') === '') { + CSS.registerProperty({ + name: '--spatial-navigation-function', + syntax: 'normal | grid', + inherits: false, + initialValue: 'normal' + }); + } + } + } + + /** + * Add event handlers for the spatial navigation behavior. + * This function defines which input methods trigger the spatial navigation behavior. + * @function spatialNavigationHandler + */ + function spatialNavigationHandler() { + /* + * keydown EventListener : + * If arrow key pressed, get the next focusing element and send it to focusing controller + */ + window.addEventListener('keydown', (e) => { + const currentKeyMode = (parent && parent.__spatialNavigation__.keyMode) || window.__spatialNavigation__.keyMode; + const eventTarget = document.activeElement; + const dir = ARROW_KEY_CODE[e.keyCode]; + + if (e.keyCode === TAB_KEY_CODE) { + startingPoint = null; + } + + if (!currentKeyMode || + (currentKeyMode === 'NONE') || + ((currentKeyMode === 'SHIFTARROW') && !e.shiftKey) || + ((currentKeyMode === 'ARROW') && e.shiftKey)) + return; + + if (!e.defaultPrevented) { + let focusNavigableArrowKey = {left: true, up: true, right: true, down: true}; + + // Edge case (text input, area) : Don't move focus, just navigate cursor in text area + if ((eventTarget.nodeName === 'INPUT') || eventTarget.nodeName === 'TEXTAREA') { + focusNavigableArrowKey = handlingEditableElement(e); + } + + if (focusNavigableArrowKey[dir]) { + e.preventDefault(); + mapOfBoundRect = new Map(); + + navigate(dir); + + mapOfBoundRect = null; + startingPoint = null; + } + } + }); + + /* + * mouseup EventListener : + * If the mouse click a point in the page, the point will be the starting point. + * NOTE: Let UA set the spatial navigation starting point based on click + */ + document.addEventListener('mouseup', (e) => { + startingPoint = {x: e.clientX, y: e.clientY}; + }); + + /* + * focusin EventListener : + * When the element get the focus, save it and its DOMRect for resetting the search origin + * if it disappears. + */ + window.addEventListener('focusin', (e) => { + if (e.target !== window) { + savedSearchOrigin.element = e.target; + savedSearchOrigin.rect = e.target.getBoundingClientRect(); + } + }); + } + + /** + * Enable the author to trigger spatial navigation programmatically, as if the user had done so manually. + * @see {@link https://drafts.csswg.org/css-nav-1/#dom-window-navigate} + * @function navigate + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + */ + function navigate(dir) { + // spatial navigation steps + + // 1 + const searchOrigin = findSearchOrigin(); + let eventTarget = searchOrigin; + + let elementFromPosition = null; + + // 2 Optional step, UA defined starting point + if (startingPoint) { + // if there is a starting point, set eventTarget as the element from position for getting the spatnav container + elementFromPosition = document.elementFromPoint(startingPoint.x, startingPoint.y); + + // Use starting point if the starting point isn't inside the focusable element (but not container) + // * Starting point is meaningfull when: + // 1) starting point is inside the spatnav container + // 2) starting point is inside the non-focusable element + if (elementFromPosition === null) { + elementFromPosition = document.body; + } + if (isFocusable(elementFromPosition) && !isContainer(elementFromPosition)) { + startingPoint = null; + } else if (isContainer(elementFromPosition)) { + eventTarget = elementFromPosition; + } else { + eventTarget = elementFromPosition.getSpatialNavigationContainer(); + } + } + + // 4 + if (eventTarget === document || eventTarget === document.documentElement) { + eventTarget = document.body || document.documentElement; + } + + // 5 + // At this point, spatialNavigationSearch can be applied. + // If startingPoint is either a scroll container or the document, + // find the best candidate within startingPoint + let container = null; + if ((isContainer(eventTarget) || eventTarget.nodeName === 'BODY') && !(eventTarget.nodeName === 'INPUT')) { + if (eventTarget.nodeName === 'IFRAME') { + eventTarget = eventTarget.contentDocument.documentElement; + } + container = eventTarget; + let bestInsideCandidate = null; + + // 5-2 + if ((document.activeElement === searchOrigin) || + (document.activeElement === document.body) && (searchOrigin === document.documentElement)) { + if (getCSSSpatNavAction(eventTarget) === 'scroll') { + if (scrollingController(eventTarget, dir)) return; + } else if (getCSSSpatNavAction(eventTarget) === 'focus') { + bestInsideCandidate = eventTarget.spatialNavigationSearch(dir, {container: eventTarget, candidates: getSpatialNavigationCandidates(eventTarget, {mode: 'all'})}); + if (focusingController(bestInsideCandidate, dir)) return; + } else if (getCSSSpatNavAction(eventTarget) === 'auto') { + bestInsideCandidate = eventTarget.spatialNavigationSearch(dir, {container: eventTarget}); + if (focusingController(bestInsideCandidate, dir) || scrollingController(eventTarget, dir)) return; + } + } else { + // when the previous search origin became offscreen + container = container.getSpatialNavigationContainer(); + } + } + + // 6 + // Let container be the nearest ancestor of eventTarget + container = eventTarget.getSpatialNavigationContainer(); + let parentContainer = (container.parentElement) ? container.getSpatialNavigationContainer() : null; + + // When the container is the viewport of a browsing context + if (!parentContainer && ( window.location !== window.parent.location)) { + parentContainer = window.parent.document.documentElement; + } + + if (getCSSSpatNavAction(container) === 'scroll') { + if (scrollingController(container, dir)) return; + } else if (getCSSSpatNavAction(container) === 'focus') { + navigateChain(eventTarget, container, parentContainer, dir, 'all'); + } else if (getCSSSpatNavAction(container) === 'auto') { + navigateChain(eventTarget, container, parentContainer, dir, 'visible'); + } + } + + /** + * Move the focus to the best candidate or do nothing. + * @function focusingController + * @param bestCandidate {Node} - The best candidate of the spatial navigation + * @param dir {SpatialNavigationDirection}- The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function focusingController(bestCandidate, dir) { + // 10 & 11 + // When bestCandidate is found + if (bestCandidate) { + // When bestCandidate is a focusable element and not a container : move focus + /* + * [event] navbeforefocus : Fired before spatial or sequential navigation changes the focus. + */ + if (!createSpatNavEvents('beforefocus', bestCandidate, null, dir)) + return true; + + const container = bestCandidate.getSpatialNavigationContainer(); + + if ((container !== window) && (getCSSSpatNavAction(container) === 'focus')) { + bestCandidate.focus(); + } else { + bestCandidate.focus({preventScroll: true}); + } + + startingPoint = null; + return true; + } + + // When bestCandidate is not found within the scrollport of a container: Nothing + return false; + } + + /** + * Directionally scroll the scrollable spatial navigation container if it can be manually scrolled more. + * @function scrollingController + * @param container {Node} - The spatial navigation container which can scroll + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function scrollingController(container, dir) { + + // If there is any scrollable area among parent elements and it can be manually scrolled, scroll the document + if (isScrollable(container, dir) && !isScrollBoundary(container, dir)) { + moveScroll(container, dir); + return true; + } + + // If the spatnav container is document and it can be scrolled, scroll the document + if (!container.parentElement && !isHTMLScrollBoundary(container, dir)) { + moveScroll(container.ownerDocument.documentElement, dir); + return true; + } + return false; + } + + /** + * Find the candidates within a spatial navigation container include delegable container. + * This function does not search inside delegable container or focusable container. + * In other words, this return candidates set is not included focusable elements inside delegable container or focusable container. + * + * @function getSpatialNavigationCandidates + * @param container {Node} - The spatial navigation container + * @param option {FocusableAreasOptions} - 'mode' attribute takes 'visible' or 'all' for searching the boundary of focusable elements. + * Default value is 'visible'. + * @returns {sequence} candidate elements within the container + */ + function getSpatialNavigationCandidates (container, option = {mode: 'visible'}) { + let candidates = []; + + if (container.childElementCount > 0) { + if (!container.parentElement) { + container = container.getElementsByTagName('body')[0] || document.body; + } + const children = container.children; + for (const elem of children) { + if (isDelegableContainer(elem)) { + candidates.push(elem); + } else if (isFocusable(elem)) { + candidates.push(elem); + + if (!isContainer(elem) && elem.childElementCount) { + candidates = candidates.concat(getSpatialNavigationCandidates(elem, {mode: 'all'})); + } + } else if (elem.childElementCount) { + candidates = candidates.concat(getSpatialNavigationCandidates(elem, {mode: 'all'})); + } + } + } + return (option.mode === 'all') ? candidates : candidates.filter(isVisible); + } + + /** + * Find the candidates among focusable elements within a spatial navigation container from the search origin (currently focused element) + * depending on the directional information. + * @function getFilteredSpatialNavigationCandidates + * @param element {Node} - The currently focused element which is defined as 'search origin' in the spec + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @param candidates {sequence} - The candidates for spatial navigation without the directional information + * @param container {Node} - The spatial navigation container + * @returns {Node} The candidates for spatial navigation considering the directional information + */ + function getFilteredSpatialNavigationCandidates (element, dir, candidates, container) { + const targetElement = element; + // Removed below line due to a bug. (iframe body rect is sometime weird.) + // const targetElement = (element.nodeName === 'IFRAME') ? element.contentDocument.body : element; + // If the container is unknown, get the closest container from the element + container = container || targetElement.getSpatialNavigationContainer(); + + // If the candidates is unknown, find candidates + // 5-1 + candidates = (!candidates || candidates.length <= 0) ? getSpatialNavigationCandidates(container) : candidates; + return filteredCandidates(targetElement, candidates, dir, container); + } + + /** + * Find the best candidate among the candidates within the container from the search origin (currently focused element) + * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-spatialnavigationsearch} + * @function spatialNavigationSearch + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @param candidates {sequence} - The candidates for spatial navigation + * @param container {Node} - The spatial navigation container + * @returns {Node} The best candidate which will gain the focus + */ + function spatialNavigationSearch (dir, args) { + const targetElement = this; + let internalCandidates = []; + let externalCandidates = []; + let insideOverlappedCandidates = getOverlappedCandidates(targetElement); + let bestTarget; + + // Set default parameter value + if (!args) + args = {}; + + const defaultContainer = targetElement.getSpatialNavigationContainer(); + let defaultCandidates = getSpatialNavigationCandidates(defaultContainer); + const container = args.container || defaultContainer; + if (args.container && (defaultContainer.contains(args.container))) { + defaultCandidates = defaultCandidates.concat(getSpatialNavigationCandidates(container)); + } + const candidates = (args.candidates && args.candidates.length > 0) ? + args.candidates.filter((candidate) => container.contains(candidate)) : + defaultCandidates.filter((candidate) => container.contains(candidate) && (container !== candidate)); + + // Find the best candidate + // 5 + // If startingPoint is either a scroll container or the document, + // find the best candidate within startingPoint + if (candidates && candidates.length > 0) { + + // Divide internal or external candidates + candidates.forEach(candidate => { + if (candidate !== targetElement) { + (targetElement.contains(candidate) && targetElement !== candidate ? internalCandidates : externalCandidates).push(candidate); + } + }); + + // include overlapped element to the internalCandidates + let fullyOverlapped = insideOverlappedCandidates.filter(candidate => !internalCandidates.includes(candidate)); + let overlappedContainer = candidates.filter(candidate => (isContainer(candidate) && isEntirelyVisible(targetElement, candidate))); + let overlappedByParent = overlappedContainer.map((elm) => elm.focusableAreas()).flat().filter(candidate => candidate !== targetElement); + + internalCandidates = internalCandidates.concat(fullyOverlapped).filter((candidate) => container.contains(candidate)); + externalCandidates = externalCandidates.concat(overlappedByParent).filter((candidate) => container.contains(candidate)); + + // Filter external Candidates + if (externalCandidates.length > 0) { + externalCandidates = getFilteredSpatialNavigationCandidates(targetElement, dir, externalCandidates, container); + } + + // If there isn't search origin element but search orgin rect exist (search origin isn't in the layout case) + if (searchOriginRect) { + bestTarget = selectBestCandidate(targetElement, getFilteredSpatialNavigationCandidates(targetElement, dir, internalCandidates, container), dir); + } + + if ((internalCandidates && internalCandidates.length > 0) && !(targetElement.nodeName === 'INPUT')) { + bestTarget = selectBestCandidateFromEdge(targetElement, internalCandidates, dir); + } + + bestTarget = bestTarget || selectBestCandidate(targetElement, externalCandidates, dir); + + if (bestTarget && isDelegableContainer(bestTarget)) { + // if best target is delegable container, then find descendants candidate inside delegable container. + const innerTarget = getSpatialNavigationCandidates(bestTarget, {mode: 'all'}); + const descendantsBest = innerTarget.length > 0 ? targetElement.spatialNavigationSearch(dir, {candidates: innerTarget, container: bestTarget}) : null; + if (descendantsBest) { + bestTarget = descendantsBest; + } else if (!isFocusable(bestTarget)) { + // if there is no target inside bestTarget and delegable container is not focusable, + // then try to find another best target without curren best target. + candidates.splice(candidates.indexOf(bestTarget), 1); + bestTarget = candidates.length ? targetElement.spatialNavigationSearch(dir, {candidates: candidates, container: container}) : null; + } + } + return bestTarget; + } + + return null; + } + + /** + * Get the filtered candidate among candidates. + * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate} + * @function filteredCandidates + * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec + * @param candidates {sequence} - The candidates for spatial navigation + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @param container {Node} - The spatial navigation container + * @returns {sequence} The filtered candidates which are not the search origin and not in the given spatial navigation direction from the search origin + */ + // TODO: Need to fix filtering the candidates with more clean code + function filteredCandidates(currentElm, candidates, dir, container) { + const originalContainer = currentElm.getSpatialNavigationContainer(); + let eventTargetRect; + + // If D(dir) is null, let candidates be the same as visibles + if (dir === undefined) + return candidates; + + // Offscreen handling when originalContainer is not + if (originalContainer.parentElement && container !== originalContainer && !isVisible(currentElm)) { + eventTargetRect = getBoundingClientRect(originalContainer); + } else { + eventTargetRect = searchOriginRect || getBoundingClientRect(currentElm); + } + + /* + * Else, let candidates be the subset of the elements in visibles + * whose principal box’s geometric center is within the closed half plane + * whose boundary goes through the geometric center of starting point and is perpendicular to D. + */ + if ((isContainer(currentElm) || currentElm.nodeName === 'BODY') && !(currentElm.nodeName === 'INPUT')) { + return candidates.filter(candidate => { + const candidateRect = getBoundingClientRect(candidate); + return container.contains(candidate) && + ((currentElm.contains(candidate) && isInside(eventTargetRect, candidateRect) && candidate !== currentElm) || + isOutside(candidateRect, eventTargetRect, dir)); + }); + } else { + return candidates.filter(candidate => { + const candidateRect = getBoundingClientRect(candidate); + const candidateBody = (candidate.nodeName === 'IFRAME') ? candidate.contentDocument.body : null; + return container.contains(candidate) && + candidate !== currentElm && candidateBody !== currentElm && + isOutside(candidateRect, eventTargetRect, dir) && + !isInside(eventTargetRect, candidateRect); + }); + } + } + + /** + * Select the best candidate among given candidates. + * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate} + * @function selectBestCandidate + * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec + * @param candidates {sequence} - The candidates for spatial navigation + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Node} The best candidate which will gain the focus + */ + function selectBestCandidate(currentElm, candidates, dir) { + const container = currentElm.getSpatialNavigationContainer(); + const spatialNavigationFunction = getComputedStyle(container).getPropertyValue('--spatial-navigation-function'); + const currentTargetRect = searchOriginRect || getBoundingClientRect(currentElm); + let distanceFunction; + let alignedCandidates; + + switch (spatialNavigationFunction) { + case 'grid': + alignedCandidates = candidates.filter(elm => isAligned(currentTargetRect, getBoundingClientRect(elm), dir)); + if (alignedCandidates.length > 0) { + candidates = alignedCandidates; + } + distanceFunction = getAbsoluteDistance; + break; + default: + distanceFunction = getDistance; + break; + } + return getClosestElement(currentElm, candidates, dir, distanceFunction); + } + + /** + * Select the best candidate among candidates by finding the closet candidate from the edge of the currently focused element (search origin). + * @see {@link https://drafts.csswg.org/css-nav-1/#select-the-best-candidate (Step 5)} + * @function selectBestCandidateFromEdge + * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec + * @param candidates {sequence} - The candidates for spatial navigation + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Node} The best candidate which will gain the focus + */ + function selectBestCandidateFromEdge(currentElm, candidates, dir) { + if (startingPoint) + return getClosestElement(currentElm, candidates, dir, getDistanceFromPoint); + else + return getClosestElement(currentElm, candidates, dir, getInnerDistance); + } + + /** + * Select the closest candidate from the currently focused element (search origin) among candidates by using the distance function. + * @function getClosestElement + * @param currentElm {Node} - The currently focused element which is defined as 'search origin' in the spec + * @param candidates {sequence} - The candidates for spatial navigation + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @param distanceFunction {function} - The distance function which measures the distance from the search origin to each candidate + * @returns {Node} The candidate which is the closest one from the search origin + */ + function getClosestElement(currentElm, candidates, dir, distanceFunction) { + let eventTargetRect = null; + if (( window.location !== window.parent.location ) && (currentElm.nodeName === 'BODY' || currentElm.nodeName === 'HTML')) { + // If the eventTarget is iframe, then get rect of it based on its containing document + // Set the iframe's position as (0,0) because the rects of elements inside the iframe don't know the real iframe's position. + eventTargetRect = window.frameElement.getBoundingClientRect(); + eventTargetRect.x = 0; + eventTargetRect.y = 0; + } else { + eventTargetRect = searchOriginRect || currentElm.getBoundingClientRect(); + } + + let minDistance = Number.POSITIVE_INFINITY; + let minDistanceElements = []; + + if (candidates) { + for (let i = 0; i < candidates.length; i++) { + const distance = distanceFunction(eventTargetRect, getBoundingClientRect(candidates[i]), dir); + + // If the same distance, the candidate will be selected in the DOM order + if (distance < minDistance) { + minDistance = distance; + minDistanceElements = [candidates[i]]; + } else if (distance === minDistance) { + minDistanceElements.push(candidates[i]); + } + } + } + if (minDistanceElements.length === 0) + return null; + + return (minDistanceElements.length > 1 && distanceFunction === getAbsoluteDistance) ? + getClosestElement(currentElm, minDistanceElements, dir, getEuclideanDistance) : minDistanceElements[0]; + } + + /** + * Get container of an element. + * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-getspatialnavigationcontainer} + * @module Element + * @function getSpatialNavigationContainer + * @returns {Node} The spatial navigation container + */ + function getSpatialNavigationContainer() { + let container = this; + + do { + if (!container.parentElement) { + if (window.location !== window.parent.location) { + container = window.parent.document.documentElement; + } else { + container = window.document.documentElement; + } + break; + } else { + container = container.parentElement; + } + } while (!isContainer(container)); + return container; + } + + /** + * Get nearest scroll container of an element. + * @function getScrollContainer + * @param Element + * @returns {Node} The spatial navigation container + */ + function getScrollContainer(element) { + let scrollContainer = element; + + do { + if (!scrollContainer.parentElement) { + if (window.location !== window.parent.location) { + scrollContainer = window.parent.document.documentElement; + } else { + scrollContainer = window.document.documentElement; + } + break; + } else { + scrollContainer = scrollContainer.parentElement; + } + } while (!isScrollContainer(scrollContainer) || !isVisible(scrollContainer)); + + if (scrollContainer === document || scrollContainer === document.documentElement) { + scrollContainer = window; + } + + return scrollContainer; + } + + /** + * Find focusable elements within the spatial navigation container. + * @see {@link https://drafts.csswg.org/css-nav-1/#dom-element-focusableareas} + * @function focusableAreas + * @param option {FocusableAreasOptions} - 'mode' attribute takes 'visible' or 'all' for searching the boundary of focusable elements. + * Default value is 'visible'. + * @returns {sequence} All focusable elements or only visible focusable elements within the container + */ + function focusableAreas(option = {mode: 'visible'}) { + const container = this.parentElement ? this : document.body; + const focusables = Array.prototype.filter.call(container.getElementsByTagName('*'), isFocusable); + return (option.mode === 'all') ? focusables : focusables.filter(isVisible); + } + + /** + * Create the NavigationEvent: navbeforefocus, navnotarget + * @see {@link https://drafts.csswg.org/css-nav-1/#events-navigationevent} + * @function createSpatNavEvents + * @param option {string} - Type of the navigation event (beforefocus, notarget) + * @param element {Node} - The target element of the event + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + */ + function createSpatNavEvents(eventType, containerElement, currentElement, direction) { + if (['beforefocus', 'notarget'].includes(eventType)) { + const data = { + causedTarget: currentElement, + dir: direction + }; + const triggeredEvent = new CustomEvent('nav' + eventType, {bubbles: true, cancelable: true, detail: data}); + return containerElement.dispatchEvent(triggeredEvent); + } + } + + /** + * Get the value of the CSS custom property of the element + * @function readCssVar + * @param element {Node} + * @param varName {string} - The name of the css custom property without '--' + * @returns {string} The value of the css custom property + */ + function readCssVar(element, varName) { + // 20210606 fix getPropertyValue returning null ~inf + return (element.style.getPropertyValue(`--${varName}`) || '').trim(); + } + + /** + * Decide whether or not the 'contain' value is given to 'spatial-navigation-contain' css property of an element + * @function isCSSSpatNavContain + * @param element {Node} + * @returns {boolean} + */ + function isCSSSpatNavContain(element) { + return readCssVar(element, 'spatial-navigation-contain') === 'contain'; + } + + /** + * Return the value of 'spatial-navigation-action' css property of an element + * @function getCSSSpatNavAction + * @param element {Node} - would be the spatial navigation container + * @returns {string} auto | focus | scroll + */ + function getCSSSpatNavAction(element) { + return readCssVar(element, 'spatial-navigation-action') || 'auto'; + } + + /** + * Only move the focus with spatial navigation. Manually scrolling isn't available. + * @function navigateChain + * @param eventTarget {Node} - currently focused element + * @param container {SpatialNavigationContainer} - container + * @param parentContainer {SpatialNavigationContainer} - parent container + * @param option - visible || all + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + */ + function navigateChain(eventTarget, container, parentContainer, dir, option) { + let currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container}; + + while (parentContainer) { + if (focusingController(eventTarget.spatialNavigationSearch(dir, currentOption), dir)) { + return; + } else { + if ((option === 'visible') && scrollingController(container, dir)) return; + else { + if (!createSpatNavEvents('notarget', container, eventTarget, dir)) return; + + // find the container + if (container === document || container === document.documentElement) { + if ( window.location !== window.parent.location ) { + // The page is in an iframe. eventTarget needs to be reset because the position of the element in the iframe + eventTarget = window.frameElement; + container = eventTarget.ownerDocument.documentElement; + } + } else { + container = parentContainer; + } + currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container}; + let nextContainer = container.getSpatialNavigationContainer(); + + if (nextContainer !== container) { + parentContainer = nextContainer; + } else { + parentContainer = null; + } + } + } + } + + currentOption = {candidates: getSpatialNavigationCandidates(container, {mode: option}), container}; + + // Behavior after 'navnotarget' - Getting out from the current spatnav container + if ((!parentContainer && container) && focusingController(eventTarget.spatialNavigationSearch(dir, currentOption), dir)) return; + + if (!createSpatNavEvents('notarget', currentOption.container, eventTarget, dir)) return; + + if ((getCSSSpatNavAction(container) === 'auto') && (option === 'visible')) { + if (scrollingController(container, dir)) return; + } + } + + /** + * Find search origin + * @see {@link https://drafts.csswg.org/css-nav-1/#nav} + * @function findSearchOrigin + * @returns {Node} The search origin for the spatial navigation + */ + function findSearchOrigin() { + let searchOrigin = document.activeElement; + + if (!searchOrigin || (searchOrigin === document.body && !document.querySelector(':focus'))) { + // When the previous search origin lost its focus by blur: (1) disable attribute (2) visibility: hidden + if (savedSearchOrigin.element && (searchOrigin !== savedSearchOrigin.element)) { + const elementStyle = window.getComputedStyle(savedSearchOrigin.element, null); + const invisibleStyle = ['hidden', 'collapse']; + + if (savedSearchOrigin.element.disabled || invisibleStyle.includes(elementStyle.getPropertyValue('visibility'))) { + searchOrigin = savedSearchOrigin.element; + return searchOrigin; + } + } + searchOrigin = document.documentElement; + } + // When the previous search origin lost its focus by blur: (1) display:none () element size turned into zero + if (savedSearchOrigin.element && + ((getBoundingClientRect(savedSearchOrigin.element).height === 0) || (getBoundingClientRect(savedSearchOrigin.element).width === 0))) { + searchOriginRect = savedSearchOrigin.rect; + } + + if (!isVisibleInScroller(searchOrigin)) { + const scroller = getScrollContainer(searchOrigin); + if (scroller && ((scroller === window) || (getCSSSpatNavAction(scroller) === 'auto'))) + return scroller; + } + return searchOrigin; + } + + /** + * Move the scroll of an element depending on the given spatial navigation directrion + * (Assume that User Agent defined distance is '40px') + * @see {@link https://drafts.csswg.org/css-nav-1/#directionally-scroll-an-element} + * @function moveScroll + * @param element {Node} - The scrollable element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @param offset {Number} - The explicit amount of offset for scrolling. Default value is 0. + */ + function moveScroll(element, dir, offset = 0) { + if (element) { + switch (dir) { + case 'left': element.scrollLeft -= (40 + offset); break; + case 'right': element.scrollLeft += (40 + offset); break; + case 'up': element.scrollTop -= (40 + offset); break; + case 'down': element.scrollTop += (40 + offset); break; + } + } + } + + /** + * Decide whether an element is container or not. + * @function isContainer + * @param element {Node} element + * @returns {boolean} + */ + function isContainer(element) { + return (!element.parentElement) || + (element.nodeName === 'IFRAME') || + (isScrollContainer(element)) || + (isCSSSpatNavContain(element)); + } + + /** + * Decide whether an element is delegable container or not. + * NOTE: THIS IS NON-NORMATIVE API. + * @function isDelegableContainer + * @param element {Node} element + * @returns {boolean} + */ + function isDelegableContainer(element) { + return readCssVar(element, 'spatial-navigation-contain') === 'delegable'; + } + + /** + * Decide whether an element is a scrollable container or not. + * @see {@link https://drafts.csswg.org/css-overflow-3/#scroll-container} + * @function isScrollContainer + * @param element {Node} + * @returns {boolean} + */ + function isScrollContainer(element) { + const elementStyle = window.getComputedStyle(element, null); + const overflowX = elementStyle.getPropertyValue('overflow-x'); + const overflowY = elementStyle.getPropertyValue('overflow-y'); + + return ((overflowX !== 'visible' && overflowX !== 'clip' && isOverflow(element, 'left')) || + (overflowY !== 'visible' && overflowY !== 'clip' && isOverflow(element, 'down'))) ? + true : false; + } + + /** + * Decide whether this element is scrollable or not. + * NOTE: If the value of 'overflow' is given to either 'visible', 'clip', or 'hidden', the element isn't scrollable. + * If the value is 'hidden', the element can be only programmically scrollable. (https://drafts.csswg.org/css-overflow-3/#valdef-overflow-hidden) + * @function isScrollable + * @param element {Node} + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function isScrollable(element, dir) { // element, dir + if (element && typeof element === 'object') { + if (dir && typeof dir === 'string') { // parameter: dir, element + if (isOverflow(element, dir)) { + // style property + const elementStyle = window.getComputedStyle(element, null); + const overflowX = elementStyle.getPropertyValue('overflow-x'); + const overflowY = elementStyle.getPropertyValue('overflow-y'); + + switch (dir) { + case 'left': + /* falls through */ + case 'right': + return (overflowX !== 'visible' && overflowX !== 'clip' && overflowX !== 'hidden'); + case 'up': + /* falls through */ + case 'down': + return (overflowY !== 'visible' && overflowY !== 'clip' && overflowY !== 'hidden'); + } + } + return false; + } else { // parameter: element + return (element.nodeName === 'HTML' || element.nodeName === 'BODY') || + (isScrollContainer(element) && isOverflow(element)); + } + } + } + + /** + * Decide whether an element is overflow or not. + * @function isOverflow + * @param element {Node} + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function isOverflow(element, dir) { + if (element && typeof element === 'object') { + if (dir && typeof dir === 'string') { // parameter: element, dir + switch (dir) { + case 'left': + /* falls through */ + case 'right': + return (element.scrollWidth > element.clientWidth); + case 'up': + /* falls through */ + case 'down': + return (element.scrollHeight > element.clientHeight); + } + } else { // parameter: element + return (element.scrollWidth > element.clientWidth || element.scrollHeight > element.clientHeight); + } + return false; + } + } + + /** + * Decide whether the scrollbar of the browsing context reaches to the end or not. + * @function isHTMLScrollBoundary + * @param element {Node} - The top browsing context + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function isHTMLScrollBoundary(element, dir) { + let result = false; + switch (dir) { + case 'left': + result = element.scrollLeft === 0; + break; + case 'right': + result = (element.scrollWidth - element.scrollLeft - element.clientWidth) === 0; + break; + case 'up': + result = element.scrollTop === 0; + break; + case 'down': + result = (element.scrollHeight - element.scrollTop - element.clientHeight) === 0; + break; + } + return result; + } + + /** + * Decide whether the scrollbar of an element reaches to the end or not. + * @function isScrollBoundary + * @param element {Node} + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function isScrollBoundary(element, dir) { + if (isScrollable(element, dir)) { + const winScrollY = element.scrollTop; + const winScrollX = element.scrollLeft; + + const height = element.scrollHeight - element.clientHeight; + const width = element.scrollWidth - element.clientWidth; + + switch (dir) { + case 'left': return (winScrollX === 0); + case 'right': return (Math.abs(winScrollX - width) <= 1); + case 'up': return (winScrollY === 0); + case 'down': return (Math.abs(winScrollY - height) <= 1); + } + } + return false; + } + + /** + * Decide whether an element is inside the scorller viewport or not + * + * @function isVisibleInScroller + * @param element {Node} + * @returns {boolean} + */ + function isVisibleInScroller(element) { + const elementRect = element.getBoundingClientRect(); + let nearestScroller = getScrollContainer(element); + + let scrollerRect = null; + if (nearestScroller !== window) { + scrollerRect = getBoundingClientRect(nearestScroller); + } else { + scrollerRect = new DOMRect(0, 0, window.innerWidth, window.innerHeight); + } + + if (isInside(scrollerRect, elementRect, 'left') && isInside(scrollerRect, elementRect, 'down')) + return true; + else + return false; + } + + /** + * Decide whether an element is focusable for spatial navigation. + * 1. If element is the browsing context (document, iframe), then it's focusable, + * 2. If the element is scrollable container (regardless of scrollable axis), then it's focusable, + * 3. The value of tabIndex >= 0, then it's focusable, + * 4. If the element is disabled, it isn't focusable, + * 5. If the element is expressly inert, it isn't focusable, + * 6. Whether the element is being rendered or not. + * + * @function isFocusable + * @param element {Node} + * @returns {boolean} + * + * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#focusable-area} + */ + function isFocusable(element) { + if ((element.tabIndex < 0) || isAtagWithoutHref(element) || isActuallyDisabled(element) || isExpresslyInert(element) || !isBeingRendered(element)) + return false; + else if ((!element.parentElement) || (isScrollable(element) && isOverflow(element)) || (element.tabIndex >= 0)) + return true; + } + + /** + * Decide whether an element is a tag without href attribute or not. + * + * @function isAtagWithoutHref + * @param element {Node} + * @returns {boolean} + */ + function isAtagWithoutHref(element) { + return (element.tagName === 'A' && element.getAttribute('href') === null && element.getAttribute('tabIndex') === null); + } + + /** + * Decide whether an element is actually disabled or not. + * + * @function isActuallyDisabled + * @param element {Node} + * @returns {boolean} + * + * @see {@link https://html.spec.whatwg.org/multipage/semantics-other.html#concept-element-disabled} + */ + function isActuallyDisabled(element) { + if (['BUTTON', 'INPUT', 'SELECT', 'TEXTAREA', 'OPTGROUP', 'OPTION', 'FIELDSET'].includes(element.tagName)) + return (element.disabled); + else + return false; + } + + /** + * Decide whether the element is expressly inert or not. + * @see {@link https://html.spec.whatwg.org/multipage/interaction.html#expressly-inert} + * @function isExpresslyInert + * @param element {Node} + * @returns {boolean} + */ + function isExpresslyInert(element) { + return ((element.inert) && (!element.ownerDocument.documentElement.inert)); + } + + /** + * Decide whether the element is being rendered or not. + * 1. If an element has the style as "visibility: hidden | collapse" or "display: none", it is not being rendered. + * 2. If an element has the style as "opacity: 0", it is not being rendered.(that is, invisible). + * 3. If width and height of an element are explicitly set to 0, it is not being rendered. + * 4. If a parent element is hidden, an element itself is not being rendered. + * (CSS visibility property and display property are inherited.) + * @see {@link https://html.spec.whatwg.org/multipage/rendering.html#being-rendered} + * @function isBeingRendered + * @param element {Node} + * @returns {boolean} + */ + function isBeingRendered(element) { + if (!isVisibleStyleProperty(element.parentElement)) + return false; + if (!isVisibleStyleProperty(element) || (element.style.opacity === '0') || + (window.getComputedStyle(element).height === '0px' || window.getComputedStyle(element).width === '0px')) + return false; + return true; + } + + /** + * Decide whether this element is partially or completely visible to user agent. + * @function isVisible + * @param element {Node} + * @returns {boolean} + */ + function isVisible(element) { + return (!element.parentElement) || (isVisibleStyleProperty(element) && hitTest(element)); + } + + /** + * Decide whether this element is completely visible in this viewport for the arrow direction. + * @function isEntirelyVisible + * @param element {Node} + * @returns {boolean} + */ + function isEntirelyVisible(element, container) { + const rect = getBoundingClientRect(element); + const containerElm = container || element.getSpatialNavigationContainer(); + const containerRect = getBoundingClientRect(containerElm); + + // FIXME: when element is bigger than container? + const entirelyVisible = !((rect.left < containerRect.left) || + (rect.right > containerRect.right) || + (rect.top < containerRect.top) || + (rect.bottom > containerRect.bottom)); + + return entirelyVisible; + } + + /** + * Decide the style property of this element is specified whether it's visible or not. + * @function isVisibleStyleProperty + * @param element {CSSStyleDeclaration} + * @returns {boolean} + */ + function isVisibleStyleProperty(element) { + const elementStyle = window.getComputedStyle(element, null); + const thisVisibility = elementStyle.getPropertyValue('visibility'); + const thisDisplay = elementStyle.getPropertyValue('display'); + const invisibleStyle = ['hidden', 'collapse']; + + return (thisDisplay !== 'none' && !invisibleStyle.includes(thisVisibility)); + } + + /** + * Decide whether this element is entirely or partially visible within the viewport. + * @function hitTest + * @param element {Node} + * @returns {boolean} + */ + function hitTest(element) { + const elementRect = getBoundingClientRect(element); + if (element.nodeName !== 'IFRAME' && (elementRect.top < 0 || elementRect.left < 0 || + elementRect.top > element.ownerDocument.documentElement.clientHeight || elementRect.left >element.ownerDocument.documentElement.clientWidth)) + return false; + + let offsetX = parseInt(element.offsetWidth) / 10; + let offsetY = parseInt(element.offsetHeight) / 10; + + offsetX = isNaN(offsetX) ? 1 : offsetX; + offsetY = isNaN(offsetY) ? 1 : offsetY; + + const hitTestPoint = { + // For performance, just using the three point(middle, leftTop, rightBottom) of the element for hit testing + middle: [(elementRect.left + elementRect.right) / 2, (elementRect.top + elementRect.bottom) / 2], + leftTop: [elementRect.left + offsetX, elementRect.top + offsetY], + rightBottom: [elementRect.right - offsetX, elementRect.bottom - offsetY] + }; + + for(const point in hitTestPoint) { + const elemFromPoint = element.ownerDocument.elementFromPoint(...hitTestPoint[point]); + if (element === elemFromPoint || element.contains(elemFromPoint)) { + return true; + } + } + return false; + } + + /** + * Decide whether a child element is entirely or partially Included within container visually. + * @function isInside + * @param containerRect {DOMRect} + * @param childRect {DOMRect} + * @returns {boolean} + */ + function isInside(containerRect, childRect) { + const rightEdgeCheck = (containerRect.left <= childRect.right && containerRect.right >= childRect.right); + const leftEdgeCheck = (containerRect.left <= childRect.left && containerRect.right >= childRect.left); + const topEdgeCheck = (containerRect.top <= childRect.top && containerRect.bottom >= childRect.top); + const bottomEdgeCheck = (containerRect.top <= childRect.bottom && containerRect.bottom >= childRect.bottom); + return (rightEdgeCheck || leftEdgeCheck) && (topEdgeCheck || bottomEdgeCheck); + } + + /** + * Decide whether this element is entirely or partially visible within the viewport. + * Note: rect1 is outside of rect2 for the dir + * @function isOutside + * @param rect1 {DOMRect} + * @param rect2 {DOMRect} + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {boolean} + */ + function isOutside(rect1, rect2, dir) { + switch (dir) { + case 'left': + return isRightSide(rect2, rect1); + case 'right': + return isRightSide(rect1, rect2); + case 'up': + return isBelow(rect2, rect1); + case 'down': + return isBelow(rect1, rect2); + default: + return false; + } + } + + /* rect1 is right of rect2 */ + function isRightSide(rect1, rect2) { + return rect1.left >= rect2.right || (rect1.left >= rect2.left && rect1.right > rect2.right && rect1.bottom > rect2.top && rect1.top < rect2.bottom); + } + + /* rect1 is below of rect2 */ + function isBelow(rect1, rect2) { + return rect1.top >= rect2.bottom || (rect1.top >= rect2.top && rect1.bottom > rect2.bottom && rect1.left < rect2.right && rect1.right > rect2.left); + } + + /* rect1 is completely aligned or partially aligned for the direction */ + function isAligned(rect1, rect2, dir) { + switch (dir) { + case 'left' : + /* falls through */ + case 'right' : + return rect1.bottom > rect2.top && rect1.top < rect2.bottom; + case 'up' : + /* falls through */ + case 'down' : + return rect1.right > rect2.left && rect1.left < rect2.right; + default: + return false; + } + } + + /** + * Get distance between the search origin and a candidate element along the direction when candidate element is inside the search origin. + * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance} + * @function getDistanceFromPoint + * @param point {Point} - The search origin + * @param element {DOMRect} - A candidate element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Number} The euclidian distance between the spatial navigation container and an element inside it + */ + function getDistanceFromPoint(point, element, dir) { + point = startingPoint; + // Get exit point, entry point -> {x: '', y: ''}; + const points = getEntryAndExitPoints(dir, point, element); + + // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate + // that minimize the distance between these two points + const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x); + const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y); + + // The result is euclidian distance between P1 and P2. + return Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2)); + } + + /** + * Get distance between the search origin and a candidate element along the direction when candidate element is inside the search origin. + * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance} + * @function getInnerDistance + * @param rect1 {DOMRect} - The search origin + * @param rect2 {DOMRect} - A candidate element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Number} The euclidean distance between the spatial navigation container and an element inside it + */ + function getInnerDistance(rect1, rect2, dir) { + const baseEdgeForEachDirection = {left: 'right', right: 'left', up: 'bottom', down: 'top'}; + const baseEdge = baseEdgeForEachDirection[dir]; + + return Math.abs(rect1[baseEdge] - rect2[baseEdge]); + } + + /** + * Get the distance between the search origin and a candidate element considering the direction. + * @see {@link https://drafts.csswg.org/css-nav-1/#calculating-the-distance} + * @function getDistance + * @param searchOrigin {DOMRect | Point} - The search origin + * @param candidateRect {DOMRect} - A candidate element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Number} The distance scoring between two elements + */ + function getDistance(searchOrigin, candidateRect, dir) { + const kOrthogonalWeightForLeftRight = 30; + const kOrthogonalWeightForUpDown = 2; + + let orthogonalBias = 0; + let alignBias = 0; + const alignWeight = 5.0; + + // Get exit point, entry point -> {x: '', y: ''}; + const points = getEntryAndExitPoints(dir, searchOrigin, candidateRect); + + // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate + // that minimize the distance between these two points + const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x); + const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y); + + // A: The euclidean distance between P1 and P2. + const A = Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2)); + let B, C; + + // B: The absolute distance in the direction which is orthogonal to dir between P1 and P2, or 0 if dir is null. + // C: The intersection edges between a candidate and the starting point. + + // D: The square root of the area of intersection between the border boxes of candidate and starting point + const intersectionRect = getIntersectionRect(searchOrigin, candidateRect); + const D = intersectionRect.area; + + switch (dir) { + case 'left': + /* falls through */ + case 'right' : + // If two elements are aligned, add align bias + // else, add orthogonal bias + if (isAligned(searchOrigin, candidateRect, dir)) + alignBias = Math.min(intersectionRect.height / searchOrigin.height , 1); + else + orthogonalBias = (searchOrigin.height / 2); + + B = (P2 + orthogonalBias) * kOrthogonalWeightForLeftRight; + C = alignWeight * alignBias; + break; + + case 'up' : + /* falls through */ + case 'down' : + // If two elements are aligned, add align bias + // else, add orthogonal bias + if (isAligned(searchOrigin, candidateRect, dir)) + alignBias = Math.min(intersectionRect.width / searchOrigin.width , 1); + else + orthogonalBias = (searchOrigin.width / 2); + + B = (P1 + orthogonalBias) * kOrthogonalWeightForUpDown; + C = alignWeight * alignBias; + break; + + default: + B = 0; + C = 0; + break; + } + + return (A + B - C - D); + } + + /** + * Get the euclidean distance between the search origin and a candidate element considering the direction. + * @function getEuclideanDistance + * @param rect1 {DOMRect} - The search origin + * @param rect2 {DOMRect} - A candidate element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Number} The distance scoring between two elements + */ + function getEuclideanDistance(rect1, rect2, dir) { + // Get exit point, entry point + const points = getEntryAndExitPoints(dir, rect1, rect2); + + // Find the points P1 inside the border box of starting point and P2 inside the border box of candidate + // that minimize the distance between these two points + const P1 = Math.abs(points.entryPoint.x - points.exitPoint.x); + const P2 = Math.abs(points.entryPoint.y - points.exitPoint.y); + + // Return the euclidean distance between P1 and P2. + return Math.sqrt(Math.pow(P1, 2) + Math.pow(P2, 2)); + } + + /** + * Get the absolute distance between the search origin and a candidate element considering the direction. + * @function getAbsoluteDistance + * @param rect1 {DOMRect} - The search origin + * @param rect2 {DOMRect} - A candidate element + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD) + * @returns {Number} The distance scoring between two elements + */ + function getAbsoluteDistance(rect1, rect2, dir) { + // Get exit point, entry point + const points = getEntryAndExitPoints(dir, rect1, rect2); + + // Return the absolute distance in the dir direction between P1 and P. + return ((dir === 'left') || (dir === 'right')) ? + Math.abs(points.entryPoint.x - points.exitPoint.x) : Math.abs(points.entryPoint.y - points.exitPoint.y); + } + + /** + * Get entry point and exit point of two elements considering the direction. + * @function getEntryAndExitPoints + * @param dir {SpatialNavigationDirection} - The directional information for the spatial navigation (e.g. LRUD). Default value for dir is 'down'. + * @param searchOrigin {DOMRect | Point} - The search origin which contains the exit point + * @param candidateRect {DOMRect} - One of candidates which contains the entry point + * @returns {Points} The exit point from the search origin and the entry point from a candidate + */ + function getEntryAndExitPoints(dir = 'down', searchOrigin, candidateRect) { + /** + * User type definition for Point + * @typeof {Object} Points + * @property {Point} Points.entryPoint + * @property {Point} Points.exitPoint + */ + const points = {entryPoint: {x: 0, y: 0}, exitPoint:{x: 0, y: 0}}; + + if (startingPoint) { + points.exitPoint = searchOrigin; + + switch (dir) { + case 'left': + points.entryPoint.x = candidateRect.right; + break; + case 'up': + points.entryPoint.y = candidateRect.bottom; + break; + case 'right': + points.entryPoint.x = candidateRect.left; + break; + case 'down': + points.entryPoint.y = candidateRect.top; + break; + } + + // Set orthogonal direction + switch (dir) { + case 'left': + case 'right': + if (startingPoint.y <= candidateRect.top) { + points.entryPoint.y = candidateRect.top; + } else if (startingPoint.y < candidateRect.bottom) { + points.entryPoint.y = startingPoint.y; + } else { + points.entryPoint.y = candidateRect.bottom; + } + break; + + case 'up': + case 'down': + if (startingPoint.x <= candidateRect.left) { + points.entryPoint.x = candidateRect.left; + } else if (startingPoint.x < candidateRect.right) { + points.entryPoint.x = startingPoint.x; + } else { + points.entryPoint.x = candidateRect.right; + } + break; + } + } + else { + // Set direction + switch (dir) { + case 'left': + points.exitPoint.x = searchOrigin.left; + points.entryPoint.x = (candidateRect.right < searchOrigin.left) ? candidateRect.right : searchOrigin.left; + break; + case 'up': + points.exitPoint.y = searchOrigin.top; + points.entryPoint.y = (candidateRect.bottom < searchOrigin.top) ? candidateRect.bottom : searchOrigin.top; + break; + case 'right': + points.exitPoint.x = searchOrigin.right; + points.entryPoint.x = (candidateRect.left > searchOrigin.right) ? candidateRect.left : searchOrigin.right; + break; + case 'down': + points.exitPoint.y = searchOrigin.bottom; + points.entryPoint.y = (candidateRect.top > searchOrigin.bottom) ? candidateRect.top : searchOrigin.bottom; + break; + } + + // Set orthogonal direction + switch (dir) { + case 'left': + case 'right': + if (isBelow(searchOrigin, candidateRect)) { + points.exitPoint.y = searchOrigin.top; + points.entryPoint.y = (candidateRect.bottom < searchOrigin.top) ? candidateRect.bottom : searchOrigin.top; + } else if (isBelow(candidateRect, searchOrigin)) { + points.exitPoint.y = searchOrigin.bottom; + points.entryPoint.y = (candidateRect.top > searchOrigin.bottom) ? candidateRect.top : searchOrigin.bottom; + } else { + points.exitPoint.y = Math.max(searchOrigin.top, candidateRect.top); + points.entryPoint.y = points.exitPoint.y; + } + break; + + case 'up': + case 'down': + if (isRightSide(searchOrigin, candidateRect)) { + points.exitPoint.x = searchOrigin.left; + points.entryPoint.x = (candidateRect.right < searchOrigin.left) ? candidateRect.right : searchOrigin.left; + } else if (isRightSide(candidateRect, searchOrigin)) { + points.exitPoint.x = searchOrigin.right; + points.entryPoint.x = (candidateRect.left > searchOrigin.right) ? candidateRect.left : searchOrigin.right; + } else { + points.exitPoint.x = Math.max(searchOrigin.left, candidateRect.left); + points.entryPoint.x = points.exitPoint.x; + } + break; + } + } + + return points; + } + + /** + * Find focusable elements within the container + * @see {@link https://drafts.csswg.org/css-nav-1/#find-the-shortest-distance} + * @function getIntersectionRect + * @param rect1 {DOMRect} - The search origin which contains the exit point + * @param rect2 {DOMRect} - One of candidates which contains the entry point + * @returns {IntersectionArea} The intersection area between two elements. + * + * @typeof {Object} IntersectionArea + * @property {Number} IntersectionArea.width + * @property {Number} IntersectionArea.height + */ + function getIntersectionRect(rect1, rect2) { + const intersection_rect = {width: 0, height: 0, area: 0}; + + const new_location = [Math.max(rect1.left, rect2.left), Math.max(rect1.top, rect2.top)]; + const new_max_point = [Math.min(rect1.right, rect2.right), Math.min(rect1.bottom, rect2.bottom)]; + + intersection_rect.width = Math.abs(new_location[0] - new_max_point[0]); + intersection_rect.height = Math.abs(new_location[1] - new_max_point[1]); + + if (!(new_location[0] >= new_max_point[0] || new_location[1] >= new_max_point[1])) { + // intersecting-cases + intersection_rect.area = Math.sqrt(intersection_rect.width * intersection_rect.height); + } + + return intersection_rect; + } + + /** + * Handle the spatial navigation behavior for HTMLInputElement, HTMLTextAreaElement + * @see {@link https://developer.mozilla.org/en-US/docs/Web/HTML/Element/input|HTMLInputElement (MDN)} + * @function handlingEditableElement + * @param e {Event} - keydownEvent + * @returns {boolean} + */ + function handlingEditableElement(e) { + const SPINNABLE_INPUT_TYPES = ['email', 'date', 'month', 'number', 'time', 'week'], + TEXT_INPUT_TYPES = ['password', 'text', 'search', 'tel', 'url', null]; + const eventTarget = document.activeElement; + const focusNavigableArrowKey = {left: false, up: false, right: false, down: false}; + + const dir = ARROW_KEY_CODE[e.keyCode]; + if (dir === undefined) { + return focusNavigableArrowKey; + } + + if (SPINNABLE_INPUT_TYPES.includes(eventTarget.getAttribute('type')) && + (dir === 'up' || dir === 'down')) { + focusNavigableArrowKey[dir] = true; + } else if (TEXT_INPUT_TYPES.includes(eventTarget.getAttribute('type')) || eventTarget.nodeName === 'TEXTAREA') { + // 20210606 fix selectionStart unavailable on checkboxes ~inf + const startPosition = eventTarget.selectionStart; + const endPosition = eventTarget.selectionEnd; + if (startPosition === endPosition) { // if there isn't any selected text + if (startPosition === 0) { + focusNavigableArrowKey.left = true; + focusNavigableArrowKey.up = true; + } + if (endPosition === eventTarget.value.length) { + focusNavigableArrowKey.right = true; + focusNavigableArrowKey.down = true; + } + } + } else { // HTMLDataListElement, HTMLSelectElement, HTMLOptGroup + focusNavigableArrowKey[dir] = true; + } + + return focusNavigableArrowKey; + } + + /** + * Get the DOMRect of an element + * @function getBoundingClientRect + * @param {Node} element + * @returns {DOMRect} + */ + function getBoundingClientRect(element) { + // memoization + let rect = mapOfBoundRect && mapOfBoundRect.get(element); + if (!rect) { + const boundingClientRect = element.getBoundingClientRect(); + rect = { + top: Number(boundingClientRect.top.toFixed(2)), + right: Number(boundingClientRect.right.toFixed(2)), + bottom: Number(boundingClientRect.bottom.toFixed(2)), + left: Number(boundingClientRect.left.toFixed(2)), + width: Number(boundingClientRect.width.toFixed(2)), + height: Number(boundingClientRect.height.toFixed(2)) + }; + mapOfBoundRect && mapOfBoundRect.set(element, rect); + } + return rect; + } + + /** + * Get the candidates which is fully inside the target element in visual + * @param {Node} targetElement + * @returns {sequence} overlappedCandidates + */ + function getOverlappedCandidates(targetElement) { + const container = targetElement.getSpatialNavigationContainer(); + const candidates = container.focusableAreas(); + const overlappedCandidates = []; + + candidates.forEach(element => { + if ((targetElement !== element) && isEntirelyVisible(element, targetElement)) { + overlappedCandidates.push(element); + } + }); + + return overlappedCandidates; + } + + /** + * Get the list of the experimental APIs + * @function getExperimentalAPI + */ + function getExperimentalAPI() { + function canScroll(container, dir) { + return (isScrollable(container, dir) && !isScrollBoundary(container, dir)) || + (!container.parentElement && !isHTMLScrollBoundary(container, dir)); + } + + function findTarget(findCandidate, element, dir, option) { + let eventTarget = element; + let bestNextTarget = null; + + // 4 + if (eventTarget === document || eventTarget === document.documentElement) { + eventTarget = document.body || document.documentElement; + } + + // 5 + // At this point, spatialNavigationSearch can be applied. + // If startingPoint is either a scroll container or the document, + // find the best candidate within startingPoint + if ((isContainer(eventTarget) || eventTarget.nodeName === 'BODY') && !(eventTarget.nodeName === 'INPUT')) { + if (eventTarget.nodeName === 'IFRAME') + eventTarget = eventTarget.contentDocument.body; + + const candidates = getSpatialNavigationCandidates(eventTarget, option); + + // 5-2 + if (Array.isArray(candidates) && candidates.length > 0) { + return findCandidate ? getFilteredSpatialNavigationCandidates(eventTarget, dir, candidates) : eventTarget.spatialNavigationSearch(dir, {candidates}); + } + if (canScroll(eventTarget, dir)) { + return findCandidate ? [] : eventTarget; + } + } + + // 6 + // Let container be the nearest ancestor of eventTarget + let container = eventTarget.getSpatialNavigationContainer(); + let parentContainer = (container.parentElement) ? container.getSpatialNavigationContainer() : null; + + // When the container is the viewport of a browsing context + if (!parentContainer && ( window.location !== window.parent.location)) { + parentContainer = window.parent.document.documentElement; + } + + // 7 + while (parentContainer) { + const candidates = filteredCandidates(eventTarget, getSpatialNavigationCandidates(container, option), dir, container); + + if (Array.isArray(candidates) && candidates.length > 0) { + bestNextTarget = eventTarget.spatialNavigationSearch(dir, {candidates, container}); + if (bestNextTarget) { + return findCandidate ? candidates : bestNextTarget; + } + } + + // If there isn't any candidate and the best candidate among candidate: + // 1) Scroll or 2) Find candidates of the ancestor container + // 8 - if + else if (canScroll(container, dir)) { + return findCandidate ? [] : eventTarget; + } else if (container === document || container === document.documentElement) { + container = window.document.documentElement; + + // The page is in an iframe + if ( window.location !== window.parent.location ) { + // eventTarget needs to be reset because the position of the element in the IFRAME + // is unuseful when the focus moves out of the iframe + eventTarget = window.frameElement; + container = window.parent.document.documentElement; + if (container.parentElement) + parentContainer = container.getSpatialNavigationContainer(); + else { + parentContainer = null; + break; + } + } + } else { + // avoiding when spatnav container with tabindex=-1 + if (isFocusable(container)) { + eventTarget = container; + } + + container = parentContainer; + if (container.parentElement) + parentContainer = container.getSpatialNavigationContainer(); + else { + parentContainer = null; + break; + } + } + } + + if (!parentContainer && container) { + // Getting out from the current spatnav container + const candidates = filteredCandidates(eventTarget, getSpatialNavigationCandidates(container, option), dir, container); + + // 9 + if (Array.isArray(candidates) && candidates.length > 0) { + bestNextTarget = eventTarget.spatialNavigationSearch(dir, {candidates, container}); + if (bestNextTarget) { + return findCandidate ? candidates : bestNextTarget; + } + } + } + + if (canScroll(container, dir)) { + bestNextTarget = eventTarget; + return bestNextTarget; + } + } + + return { + isContainer, + isScrollContainer, + isVisibleInScroller, + findCandidates: findTarget.bind(null, true), + findNextTarget: findTarget.bind(null, false), + getDistanceFromTarget: (element, candidateElement, dir) => { + if ((isContainer(element) || element.nodeName === 'BODY') && !(element.nodeName === 'INPUT')) { + if (getSpatialNavigationCandidates(element).includes(candidateElement)) { + return getInnerDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir); + } + } + return getDistance(getBoundingClientRect(element), getBoundingClientRect(candidateElement), dir); + } + }; + } + + /** + * Makes to use the experimental APIs. + * @function enableExperimentalAPIs + * @param option {boolean} - If it is true, the experimental APIs can be used or it cannot. + */ + function enableExperimentalAPIs (option) { + const currentKeyMode = window.__spatialNavigation__ && window.__spatialNavigation__.keyMode; + window.__spatialNavigation__ = (option === false) ? getInitialAPIs() : Object.assign(getInitialAPIs(), getExperimentalAPI()); + window.__spatialNavigation__.keyMode = currentKeyMode; + Object.seal(window.__spatialNavigation__); + } + + /** + * Set the environment for using the spatial navigation polyfill. + * @function getInitialAPIs + */ + function getInitialAPIs() { + return { + enableExperimentalAPIs, + get keyMode() { return this._keymode ? this._keymode : 'ARROW'; }, + set keyMode(mode) { this._keymode = (['SHIFTARROW', 'ARROW', 'NONE'].includes(mode)) ? mode : 'ARROW'; }, + setStartingPoint: function (x, y) {startingPoint = (x && y) ? {x, y} : null;} + }; + } + + initiateSpatialNavigation(); + enableExperimentalAPIs(false); + + window.addEventListener('load', () => { + spatialNavigationHandler(); + }); +})(); diff --git a/src/ui.css b/src/ui.css new file mode 100644 index 00000000..1abb2b4c --- /dev/null +++ b/src/ui.css @@ -0,0 +1,34 @@ +.ytaf-ui-container { + position: absolute; + top: 10%; + left: 10%; + right: 10%; + bottom: 10%; + + background: rgba(0,0,0,0.8); + color: white; + border-radius: 20px; + padding: 20px; + z-index: 9999; + font-size: 1.5rem; +} + +.ytaf-ui-container :focus { + outline: 4px red solid; +} + +.ytaf-ui-container h1 { + margin: 0; + margin-bottom: 0.5em; + text-align: center; +} + +.ytaf-ui-container input[type=checkbox] { + width: 2rem; + height: 1.5rem; +} + +.ytaf-ui-container label { + display: block; + font-size: 2rem; +} diff --git a/src/ui.js b/src/ui.js new file mode 100644 index 00000000..e29ac9f8 --- /dev/null +++ b/src/ui.js @@ -0,0 +1,68 @@ +import './spatial-navigation-polyfill.js'; +import './ui.css'; + +// We handle key events ourselves. +window.__spatialNavigation__.keyMode = 'NONE'; + +const ARROW_KEY_CODE = {37: 'left', 38: 'up', 39: 'right', 40: 'down'}; + +const uiContainer = document.createElement('div'); + +uiContainer.classList.add('ytaf-ui-container'); +uiContainer.style['display'] = 'none'; +uiContainer.setAttribute('tabindex', 0); +uiContainer.addEventListener('focus', () => console.info('uiContainer focused!'), true); +uiContainer.addEventListener('blur', () => console.info('uiContainer blured!'), true); + +uiContainer.addEventListener("keydown", (evt) => { + console.info('uiContainer key event:', evt.type, evt.charCode); + if (evt.charCode !== 404 && evt.charCode !== 172) { + if (evt.keyCode in ARROW_KEY_CODE) { + navigate(ARROW_KEY_CODE[evt.keyCode]); + } else if (evt.keyCode === 13) { // "OK" button + document.querySelector(':focus').click(); + } else if (evt.keyCode === 27) { // Back button + uiContainer.style.display = 'none'; + uiContainer.blur(); + } + evt.preventDefault(); + evt.stopPropagation(); + } +}, true); + +uiContainer.innerHTML = ` +

webOS YouTube Extended

+ + +`; + +document.querySelector('body').appendChild(uiContainer); + +const eventHandler = (evt) => { + console.info('Key event:', evt.type, evt.charCode, evt.keyCode, evt.defaultPrevented); + if (evt.charCode == 404 || evt.charCode == 172) { + console.info('Taking over!'); + evt.preventDefault(); + evt.stopPropagation(); + if (evt.type === 'keydown') { + if (uiContainer.style.display === 'none') { + console.info('Showing and focusing!'); + uiContainer.style.display = 'block'; + uiContainer.focus(); + } else { + console.info('Hiding!'); + uiContainer.style.display = 'none'; + uiContainer.blur(); + } + } + return false; + } + return true; +}; + +// Red, Green, Yellow, Blue +// 403, 404, 405, 406 +// ---, 172, 170, 191 +document.addEventListener("keydown", eventHandler, true); +document.addEventListener("keypress", eventHandler, true); +document.addEventListener("keyup", eventHandler, true); diff --git a/src/userScript.js b/src/userScript.js index 030e765e..44838032 100644 --- a/src/userScript.js +++ b/src/userScript.js @@ -1,6 +1,8 @@ import 'whatwg-fetch'; import 'core-js/stable'; import 'regenerator-runtime/runtime'; +import './domrect-polyfill'; import './adblock.js'; import './sponsorblock.js'; +import './ui.js'; diff --git a/webpack.config.js b/webpack.config.js index f730e023..0d43b90b 100644 --- a/webpack.config.js +++ b/webpack.config.js @@ -30,6 +30,10 @@ module.exports = (env) => [ test: /\.m?js$/, use: 'babel-loader', }, + { + test: /\.css$/i, + use: ['style-loader', 'css-loader'], + }, ], }, plugins: [ From 6443502834e0c50c0f31ce04c782d13cf7550afe Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 20:52:31 +0200 Subject: [PATCH 14/24] src: initial working configuration --- src/adblock.js | 9 ++++++++- src/config.js | 29 +++++++++++++++++++++++++++++ src/index.js | 2 +- src/sponsorblock.js | 11 ++++++++--- src/ui.js | 11 +++++++++++ 5 files changed, 57 insertions(+), 5 deletions(-) create mode 100644 src/config.js diff --git a/src/adblock.js b/src/adblock.js index f715a2e1..eaa38fad 100644 --- a/src/adblock.js +++ b/src/adblock.js @@ -1,3 +1,5 @@ +import {configRead} from './config'; + const YOUTUBE_REGEX = /^https?:\/\/(\w*.)?youtube.com/i; const YOUTUBE_AD_REGEX = /(doubleclick\.net)|(adservice\.google\.)|(youtube\.com\/api\/stats\/ads)|(&ad_type=)|(&adurl=)|(-pagead-id.)|(doubleclick\.com)|(\/ad_status.)|(\/api\/ads\/)|(\/googleads)|(\/pagead\/gen_)|(\/pagead\/lvz?)|(\/pubads.)|(\/pubads_)|(\/securepubads)|(=adunit&)|(googlesyndication\.com)|(innovid\.com)|(youtube\.com\/pagead\/)|(google\.com\/pagead\/)|(flashtalking\.com)|(googleadservices\.com)|(s0\.2mdn\.net\/ads)|(www\.youtube\.com\/ptracking)|(www\.youtube\.com\/pagead)|(www\.youtube\.com\/get_midroll_)/; const YOUTUBE_ANNOTATIONS_REGEX = /^https?:\/\/(\w*.)?youtube\.com\/annotations_invideo\?/; @@ -13,6 +15,10 @@ const settings = { function isRequestBlocked(requestType, url) { console.log("[" + requestType + "] URL : " + url); + if (!configRead('enableAdBlock')) { + return false; + } + if (settings.disable_ads && YOUTUBE_AD_REGEX.test(url)) { console.log("%cBLOCK AD", "color: red;", url); return true; @@ -69,7 +75,8 @@ fetch = function () { const origParse = JSON.parse; JSON.parse = function () { const r = origParse.apply(this, arguments); - if (r.adPlacements) { + // WARNING: this function is *also* called by configRead... + if (r.adPlacements && configRead('enableAdBlock')) { r.adPlacements = []; } return r; diff --git a/src/config.js b/src/config.js new file mode 100644 index 00000000..27f21249 --- /dev/null +++ b/src/config.js @@ -0,0 +1,29 @@ +const CONFIG_KEY = 'ytaf-configuration'; +const defaultConfig = { + enableAdBlock: true, + enableSponsorBlock: true, +}; + +let localConfig; + +try { + localConfig = JSON.parse(window.localStorage[CONFIG_KEY]); +} catch (err) { + console.warn('Config read failed:', err); + localConfig = defaultConfig; +} + +export function configRead(key) { + if (localConfig[key] === undefined) { + console.warn('Populating key', key, 'with default value', defaultConfig[key]); + localConfig[key] = defaultConfig[key]; + } + + return localConfig[key]; +} + +export function configWrite(key, value) { + console.info('Setting key', key, 'to', value); + localConfig[key] = value; + window.localStorage[CONFIG_KEY] = JSON.stringify(localConfig); +} diff --git a/src/index.js b/src/index.js index 1a43e1c5..6aaa8575 100644 --- a/src/index.js +++ b/src/index.js @@ -27,4 +27,4 @@ function main() { } -main(); \ No newline at end of file +main(); diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 826d345e..6bb3961e 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -1,4 +1,5 @@ import sha256 from 'tiny-sha256'; +import {configRead} from './config'; // Copied from https://github.com/ajayyy/SponsorBlock/blob/9392d16617d2d48abb6125c00e2ff6042cb7bebe/src/config.ts#L179-L233 const barTypes = { @@ -242,8 +243,12 @@ window.addEventListener("hashchange", (evt) => { window.sponsorblock = null; } - const video = document.querySelector('video'); - window.sponsorblock = new SponsorBlockHandler(videoID, video); - window.sponsorblock.init(); + if (configRead('enableSponsorBlock')) { + const video = document.querySelector('video'); + window.sponsorblock = new SponsorBlockHandler(videoID, video); + window.sponsorblock.init(); + } else { + console.info('SponsorBlock disabled, not loading'); + } } }, false); diff --git a/src/ui.js b/src/ui.js index e29ac9f8..4fae86d4 100644 --- a/src/ui.js +++ b/src/ui.js @@ -1,5 +1,6 @@ import './spatial-navigation-polyfill.js'; import './ui.css'; +import {configRead, configWrite} from './config.js'; // We handle key events ourselves. window.__spatialNavigation__.keyMode = 'NONE'; @@ -38,6 +39,16 @@ uiContainer.innerHTML = ` document.querySelector('body').appendChild(uiContainer); +uiContainer.querySelector('#__adblock').checked = configRead('enableAdBlock'); +uiContainer.querySelector('#__adblock').addEventListener('change', (evt) => { + configWrite('enableAdBlock', evt.target.checked); +}); + +uiContainer.querySelector('#__sponsorblock').checked = configRead('enableSponsorBlock'); +uiContainer.querySelector('#__sponsorblock').addEventListener('change', (evt) => { + configWrite('enableSponsorBlock', evt.target.checked); +}); + const eventHandler = (evt) => { console.info('Key event:', evt.type, evt.charCode, evt.keyCode, evt.defaultPrevented); if (evt.charCode == 404 || evt.charCode == 172) { From fb03a265deac51aeb4f2a527831c0785ceadbba4 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 22:46:04 +0200 Subject: [PATCH 15/24] src: add sponsorblock webpage reference --- src/ui.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ui.js b/src/ui.js index 4fae86d4..7420cb0d 100644 --- a/src/ui.js +++ b/src/ui.js @@ -34,7 +34,7 @@ uiContainer.addEventListener("keydown", (evt) => { uiContainer.innerHTML = `

webOS YouTube Extended

- + `; document.querySelector('body').appendChild(uiContainer); From 61498c825f107a2295388bdb9b3aebec82861f31 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 23:03:04 +0200 Subject: [PATCH 16/24] index: fix slow DIAL launches --- assets/appinfo.json | 3 ++- src/index.js | 9 ++++++--- 2 files changed, 8 insertions(+), 4 deletions(-) diff --git a/assets/appinfo.json b/assets/appinfo.json index aaab3af1..0a70cd98 100644 --- a/assets/appinfo.json +++ b/assets/appinfo.json @@ -13,7 +13,8 @@ "supportsAudioGuidance": true }, "vendorExtension": { - "userAgent": "$browserName$/$browserVersion$ ($platformName$-$platformVersion$), _TV_$chipSetName$/$firmwareVersion$ (LG, $modelName$, $networkMode$)" + "userAgent": "$browserName$/$browserVersion$ ($platformName$-$platformVersion$), _TV_$chipSetName$/$firmwareVersion$ (LG, $modelName$, $networkMode$)", + "allowCrossDomain": true }, "trustLevel": "netcast", "privilegedJail": true, diff --git a/src/index.js b/src/index.js index 6aaa8575..adfcb24b 100644 --- a/src/index.js +++ b/src/index.js @@ -13,7 +13,7 @@ function concatenateUrlAndGetParams(ytUrl, path) { if (!path) { return ytUrl; } else { - return ytUrl + "?" + path; + return ytUrl + "#?" + path; } } @@ -22,8 +22,11 @@ function main() { ? window.PalmSystem.launchParams : window.launchParams; const youtubeLaunchUrlPath = extractLaunchUrlParams(launchParameters); - - window.location = concatenateUrlAndGetParams(YOUTUBE_TV_URL, youtubeLaunchUrlPath); + if (youtubeLaunchUrlPath.indexOf('https://www.youtube.com/tv?') === 0) { + window.location = youtubeLaunchUrlPath; + } else { + window.location = concatenateUrlAndGetParams(YOUTUBE_TV_URL, youtubeLaunchUrlPath); + } } From b81e943fe52b85134d548ab3de3f9eb8b7df6585 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sun, 6 Jun 2021 23:18:06 +0200 Subject: [PATCH 17/24] appinfo: reenable splashscreen Just to make it less confusing... --- assets/appinfo.json | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/assets/appinfo.json b/assets/appinfo.json index 0a70cd98..1bf9326d 100644 --- a/assets/appinfo.json +++ b/assets/appinfo.json @@ -20,6 +20,5 @@ "privilegedJail": true, "supportQuickStart": true, "dialAppName": "YouTube", - "disableBackHistoryAPI": true, - "noSplashOnLaunch": true + "disableBackHistoryAPI": true } From 394f5b79ae746bfcfdac390fcd7d00c52721a6e0 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Mon, 7 Jun 2021 22:14:48 +0200 Subject: [PATCH 18/24] index: fix normal app launch... --- src/index.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/index.js b/src/index.js index adfcb24b..9c26f5e4 100644 --- a/src/index.js +++ b/src/index.js @@ -22,7 +22,7 @@ function main() { ? window.PalmSystem.launchParams : window.launchParams; const youtubeLaunchUrlPath = extractLaunchUrlParams(launchParameters); - if (youtubeLaunchUrlPath.indexOf('https://www.youtube.com/tv?') === 0) { + if (typeof youtubeLaunchUrlPath === 'string' && youtubeLaunchUrlPath.indexOf('https://www.youtube.com/tv?') === 0) { window.location = youtubeLaunchUrlPath; } else { window.location = concatenateUrlAndGetParams(YOUTUBE_TV_URL, youtubeLaunchUrlPath); From dfe773a09ce0b304ba74dd63e96af4a97683a61a Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Mon, 7 Jun 2021 23:29:30 +0200 Subject: [PATCH 19/24] adblock: remove big homescreen ads... --- src/adblock.js | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/src/adblock.js b/src/adblock.js index eaa38fad..27bbcd38 100644 --- a/src/adblock.js +++ b/src/adblock.js @@ -75,9 +75,15 @@ fetch = function () { const origParse = JSON.parse; JSON.parse = function () { const r = origParse.apply(this, arguments); - // WARNING: this function is *also* called by configRead... if (r.adPlacements && configRead('enableAdBlock')) { r.adPlacements = []; } + + // Drop "masthead" ad from home screen + if (r?.contents?.tvBrowseRenderer?.content?.tvSurfaceContentRenderer?.content?.sectionListRenderer?.contents && configRead('enableAdBlock')) { + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = \ + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter(elm => !elm.tvMastheadRenderer); + } + return r; }; From cf814b5bb2c7d2133047226977c67ce163b40143 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Tue, 8 Jun 2021 08:27:35 +0200 Subject: [PATCH 20/24] adblocK: fix typo --- src/adblock.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/adblock.js b/src/adblock.js index 27bbcd38..d251d4f3 100644 --- a/src/adblock.js +++ b/src/adblock.js @@ -81,7 +81,7 @@ JSON.parse = function () { // Drop "masthead" ad from home screen if (r?.contents?.tvBrowseRenderer?.content?.tvSurfaceContentRenderer?.content?.sectionListRenderer?.contents && configRead('enableAdBlock')) { - r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = \ + r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents = r.contents.tvBrowseRenderer.content.tvSurfaceContentRenderer.content.sectionListRenderer.contents.filter(elm => !elm.tvMastheadRenderer); } From 99d4103b9572df049860309c127faa79034c6dad Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Wed, 9 Jun 2021 08:02:42 +0200 Subject: [PATCH 21/24] sponsorblock: add skip notifications --- src/sponsorblock.js | 86 +++++++++++++++++---------------------------- src/ui.css | 30 +++++++++++++++- src/ui.js | 33 ++++++++++++++++- 3 files changed, 94 insertions(+), 55 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 6bb3961e..5296c182 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -1,60 +1,39 @@ import sha256 from 'tiny-sha256'; import {configRead} from './config'; +import {showNotification} from './ui'; // Copied from https://github.com/ajayyy/SponsorBlock/blob/9392d16617d2d48abb6125c00e2ff6042cb7bebe/src/config.ts#L179-L233 const barTypes = { - "preview-chooseACategory": { - color: "#ffffff", - opacity: "0.7" - }, - "sponsor": { - color: "#00d400", - opacity: "0.7" - }, - "preview-sponsor": { - color: "#007800", - opacity: "0.7" - }, - "intro": { - color: "#00ffff", - opacity: "0.7" - }, - "preview-intro": { - color: "#008080", - opacity: "0.7" - }, - "outro": { - color: "#0202ed", - opacity: "0.7" - }, - "preview-outro": { - color: "#000070", - opacity: "0.7" - }, - "interaction": { - color: "#cc00ff", - opacity: "0.7" - }, - "preview-interaction": { - color: "#6c0087", - opacity: "0.7" - }, - "selfpromo": { - color: "#ffff00", - opacity: "0.7" - }, - "preview-selfpromo": { - color: "#bfbf35", - opacity: "0.7" - }, - "music_offtopic": { - color: "#ff9900", - opacity: "0.7" - }, - "preview-music_offtopic": { - color: "#a6634a", - opacity: "0.7" - } + "sponsor": { + color: "#00d400", + opacity: "0.7", + name: "sponsored segment", + }, + "intro": { + color: "#00ffff", + opacity: "0.7", + name: "intro", + }, + "outro": { + color: "#0202ed", + opacity: "0.7", + name: "outro", + }, + "interaction": { + color: "#cc00ff", + opacity: "0.7", + name: "interaction reminder", + }, + "selfpromo": { + color: "#ffff00", + opacity: "0.7", + name: "self-promotion", + }, + "music_offtopic": { + color: "#ff9900", + opacity: "0.7", + name: "non-music part", + }, }; class SponsorBlockHandler { @@ -180,7 +159,8 @@ class SponsorBlockHandler { return; } - console.info(this.videoID, 'Skipping', nextSegments[0]); + const skipName = barTypes[segment.category]?.name || segment.category; + showNotification(`Skipping ${skipName}`); this.video.currentTime = end; this.scheduleSkip(); }, (start - this.video.currentTime) * 1000); diff --git a/src/ui.css b/src/ui.css index 1abb2b4c..22c55d91 100644 --- a/src/ui.css +++ b/src/ui.css @@ -9,8 +9,8 @@ color: white; border-radius: 20px; padding: 20px; - z-index: 9999; font-size: 1.5rem; + z-index: 1000; } .ytaf-ui-container :focus { @@ -32,3 +32,31 @@ display: block; font-size: 2rem; } + +.ytaf-notification-container { + position: absolute; + right: 10px; + bottom: 10px; + font-size: 16pt; + z-index: 1200; +} + +.ytaf-notification-container .message { + background: rgba(0,0,0,0.7); + color: white; + padding: 1em; + margin: 0.5em; + transition: all 0.3s ease-in-out; + opacity: 1; + line-height: 1; + border-right: 10px solid rgba(50,255,50,0.3); + display: inline-block; + float: right; +} + +.ytaf-notification-container .message-hidden { + opacity: 0; + margin: 0 0.5em; + padding: 0 1em; + line-height: 0; +} diff --git a/src/ui.js b/src/ui.js index 7420cb0d..e715d2f4 100644 --- a/src/ui.js +++ b/src/ui.js @@ -8,7 +8,6 @@ window.__spatialNavigation__.keyMode = 'NONE'; const ARROW_KEY_CODE = {37: 'left', 38: 'up', 39: 'right', 40: 'down'}; const uiContainer = document.createElement('div'); - uiContainer.classList.add('ytaf-ui-container'); uiContainer.style['display'] = 'none'; uiContainer.setAttribute('tabindex', 0); @@ -77,3 +76,35 @@ const eventHandler = (evt) => { document.addEventListener("keydown", eventHandler, true); document.addEventListener("keypress", eventHandler, true); document.addEventListener("keyup", eventHandler, true); + + +export function showNotification(text, time=3000) { + if (!document.querySelector('.ytaf-notification-container')) { + console.info('Adding notification container'); + const c = document.createElement('div'); + c.classList.add('ytaf-notification-container'); + document.body.appendChild(c); + } + + const elm = document.createElement('div'); + const elmInner = document.createElement('div'); + elmInner.innerText = text; + elmInner.classList.add('message'); + elmInner.classList.add('message-hidden'); + elm.appendChild(elmInner); + document.querySelector('.ytaf-notification-container').appendChild(elm); + + setTimeout(() => { + elmInner.classList.remove('message-hidden'); + }, 100); + setTimeout(() => { + elmInner.classList.add('message-hidden'); + setTimeout(() => { + elm.remove(); + }, 1000); + }, time); +} + +setTimeout(() => { + showNotification('Press [GREEN] to open YTAF configuration screen'); +}, 500); From f724ca4ec45e02091cee690c6cb0a9e0baba5198 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Wed, 9 Jun 2021 08:03:23 +0200 Subject: [PATCH 22/24] sponsorblock: fix video render race condition --- src/sponsorblock.js | 74 +++++++++++++++++++++++++++++++-------------- 1 file changed, 52 insertions(+), 22 deletions(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 5296c182..19e4a7dd 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -37,10 +37,20 @@ const barTypes = { }; class SponsorBlockHandler { - constructor(videoID, video) { + video = null; + active = true; + + attachVideoTimeout = null; + nextSkipTimeout = null; + sliderInterval = null; + + observer = null; + scheduleSkipHandler = null; + durationChangeHandler = null; + segments = null; + + constructor(videoID) { this.videoID = videoID; - this.video = video; - this.active = true; } async init() { @@ -59,17 +69,30 @@ class SponsorBlockHandler { this.segments = result.segments; - console.info(this.videoID, 'Video found, binding...'); - this.scheduleSkipHandler = () => this.scheduleSkip(); this.durationChangeHandler = () => this.buildOverlay(); + this.attachVideo(); + this.buildOverlay(); + } + + attachVideo() { + clearTimeout(this.attachVideoTimeout); + this.attachVideoTimeout = null; + + this.video = document.querySelector('video'); + if (!this.video) { + console.info(this.videoID, 'No video yet...'); + this.attachVideoTimeout = setTimeout(() => this.attachVideo(), 100); + return; + } + + console.info(this.videoID, 'Video found, binding...'); + this.video.addEventListener('play', this.scheduleSkipHandler); this.video.addEventListener('pause', this.scheduleSkipHandler); this.video.addEventListener('timeupdate', this.scheduleSkipHandler); this.video.addEventListener('durationchange', this.durationChangeHandler); - - this.buildOverlay(); } buildOverlay() { @@ -78,7 +101,7 @@ class SponsorBlockHandler { return; } - if (!this.video.duration) { + if (!this.video || !this.video.duration) { console.info('No video duration yet'); return; } @@ -126,8 +149,8 @@ class SponsorBlockHandler { } scheduleSkip() { - clearTimeout(this.nextSkip); - this.nextSkip = null; + clearTimeout(this.nextSkipTimeout); + this.nextSkipTimeout = null; if (!this.active) { console.info(this.videoID, 'No longer active, ignoring...'); @@ -150,10 +173,11 @@ class SponsorBlockHandler { return; } - const [start, end] = nextSegments[0].segment; - console.info(this.videoID, 'Scheduling skip of', nextSegments[0], 'in', start - this.video.currentTime); + const [segment] = nextSegments; + const [start, end] = segment.segment; + console.info(this.videoID, 'Scheduling skip of', segment, 'in', start - this.video.currentTime); - this.nextSkip = setTimeout(() => { + this.nextSkipTimeout = setTimeout(() => { if (this.video.paused) { console.info(this.videoID, 'Currently paused, ignoring...'); return; @@ -171,9 +195,14 @@ class SponsorBlockHandler { this.active = false; - if (this.nextSkip) { - clearTimeout(this.nextSkip); - this.nextSkip = null; + if (this.nextSkipTimeout) { + clearTimeout(this.nextSkipTimeout); + this.nextSkipTimeout = null; + } + + if (this.attachVideoTimeout) { + clearTimeout(this.attachVideoTimeout); + this.attachVideoTimeout = null; } if (this.sliderInterval) { @@ -191,10 +220,12 @@ class SponsorBlockHandler { this.segmentsoverlay = null; } - this.video.removeEventListener('play', this.scheduleSkipHandler); - this.video.removeEventListener('pause', this.scheduleSkipHandler); - this.video.removeEventListener('timeupdate', this.scheduleSkipHandler); - this.video.removeEventListener('durationchange', this.durationChangeHandler); + if (this.video) { + this.video.removeEventListener('play', this.scheduleSkipHandler); + this.video.removeEventListener('pause', this.scheduleSkipHandler); + this.video.removeEventListener('timeupdate', this.scheduleSkipHandler); + this.video.removeEventListener('durationchange', this.durationChangeHandler); + } } } @@ -224,8 +255,7 @@ window.addEventListener("hashchange", (evt) => { } if (configRead('enableSponsorBlock')) { - const video = document.querySelector('video'); - window.sponsorblock = new SponsorBlockHandler(videoID, video); + window.sponsorblock = new SponsorBlockHandler(videoID); window.sponsorblock.init(); } else { console.info('SponsorBlock disabled, not loading'); From 4910cffb055a5ec48cf1e688ede56c5aa87c4062 Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Wed, 9 Jun 2021 08:11:41 +0200 Subject: [PATCH 23/24] sponsorblock: fix back-to-back skips by increasing lookback window... --- src/sponsorblock.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/sponsorblock.js b/src/sponsorblock.js index 19e4a7dd..feb796e5 100644 --- a/src/sponsorblock.js +++ b/src/sponsorblock.js @@ -165,7 +165,7 @@ class SponsorBlockHandler { // Sometimes timeupdate event (that calls scheduleSkip) gets fired right before // already scheduled skip routine below. Let's just look back a little bit // and, in worst case, perform a skip at negative interval (immediately)... - const nextSegments = this.segments.filter(seg => seg.segment[0] > this.video.currentTime - 0.1 && seg.segment[1] > this.video.currentTime - 0.1); + const nextSegments = this.segments.filter(seg => seg.segment[0] > this.video.currentTime - 0.3 && seg.segment[1] > this.video.currentTime - 0.3); nextSegments.sort((s1, s2) => s1.segment[0] - s2.segment[0]); if (!nextSegments.length) { @@ -184,6 +184,7 @@ class SponsorBlockHandler { } const skipName = barTypes[segment.category]?.name || segment.category; + console.info(this.videoID, 'Skipping', segment); showNotification(`Skipping ${skipName}`); this.video.currentTime = end; this.scheduleSkip(); From 4f8fc37d8b92b95c5268297838f0bbae1bce189a Mon Sep 17 00:00:00 2001 From: Piotr Dobrowolski Date: Sat, 12 Jun 2021 18:20:10 +0200 Subject: [PATCH 24/24] userscript: support relaunches --- src/index.js | 29 ++--------------------------- src/userScript.js | 7 +++++++ src/utils.js | 24 ++++++++++++++++++++++++ 3 files changed, 33 insertions(+), 27 deletions(-) create mode 100644 src/utils.js diff --git a/src/index.js b/src/index.js index 9c26f5e4..e6a606f5 100644 --- a/src/index.js +++ b/src/index.js @@ -1,32 +1,7 @@ -const CONTENT_TARGET_LAUNCH_PARAMETER = "contentTarget"; -const YOUTUBE_TV_URL = "https://www.youtube.com/tv"; - -function extractLaunchUrlParams(launchParameters) { - if (launchParameters === null || launchParameters === "") { - return null; - } - const launchParamJson = JSON.parse(launchParameters); - return launchParamJson[CONTENT_TARGET_LAUNCH_PARAMETER]; -} - -function concatenateUrlAndGetParams(ytUrl, path) { - if (!path) { - return ytUrl; - } else { - return ytUrl + "#?" + path; - } -} +import {extractLaunchParams, handleLaunch} from './utils'; function main() { - const launchParameters = window.PalmSystem - ? window.PalmSystem.launchParams - : window.launchParams; - const youtubeLaunchUrlPath = extractLaunchUrlParams(launchParameters); - if (typeof youtubeLaunchUrlPath === 'string' && youtubeLaunchUrlPath.indexOf('https://www.youtube.com/tv?') === 0) { - window.location = youtubeLaunchUrlPath; - } else { - window.location = concatenateUrlAndGetParams(YOUTUBE_TV_URL, youtubeLaunchUrlPath); - } + handleLaunch(extractLaunchParams()); } diff --git a/src/userScript.js b/src/userScript.js index 44838032..0577ba8f 100644 --- a/src/userScript.js +++ b/src/userScript.js @@ -3,6 +3,13 @@ import 'core-js/stable'; import 'regenerator-runtime/runtime'; import './domrect-polyfill'; +import {handleLaunch} from './utils'; + +document.addEventListener('webOSRelaunch', (evt) => { + console.info('RELAUNCH:', evt, window.launchParams); + handleLaunch(evt.detail); +}, true); + import './adblock.js'; import './sponsorblock.js'; import './ui.js'; diff --git a/src/utils.js b/src/utils.js new file mode 100644 index 00000000..c8f0875f --- /dev/null +++ b/src/utils.js @@ -0,0 +1,24 @@ +export function extractLaunchParams() { + if (window.launchParams) { + return JSON.parse(window.launchParams); + } else { + return {}; + } +} + +export function handleLaunch(params) { + const { contentTarget } = params; + + if (contentTarget && typeof contentTarget === 'string') { + if (contentTarget.indexOf('https://www.youtube.com/tv?') === 0) { + console.info('Launching from direct contentTarget:', contentTarget); + window.location = contentTarget; + } else { + console.info('Launching from partial contentTarget:', contentTarget); + window.location = 'https://www.youtube.com/tv#?' + contentTarget; + } + } else { + console.info('Default launch'); + window.location = 'https://www.youtube.com/tv'; + } +}