diff --git a/.gitignore b/.gitignore index 5180999f..2b8fe9cc 100644 --- a/.gitignore +++ b/.gitignore @@ -32,3 +32,6 @@ build/ /frontend/dist/* !/frontend/dist/.gitkeep + +default.nix +Dockerfile.dev \ No newline at end of file diff --git a/frontend/.eslintrc.json b/frontend/.eslintrc.json index 607f9b61..eaa93e2c 100644 --- a/frontend/.eslintrc.json +++ b/frontend/.eslintrc.json @@ -3,18 +3,26 @@ "env": { "node": true }, + "parser": "vue-eslint-parser", "extends": [ - "plugin:vue/vue3-essential", "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended", + "plugin:vue/vue3-essential", "@vue/eslint-config-prettier" + ], "rules": { "vue/multi-word-component-names": "off", "vue/no-reserved-component-names": "warn", - "vue/no-mutating-props": "warn" + "vue/no-mutating-props": "warn", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/ban-ts-comment": "off", + "no-undef": "off" }, "parserOptions": { "ecmaVersion": "latest", - "sourceType": "module" + "sourceType": "module", + "parser": "@typescript-eslint/parser" } } diff --git a/frontend/index.html b/frontend/index.html index 7d0b8bc9..02c303ae 100644 --- a/frontend/index.html +++ b/frontend/index.html @@ -187,6 +187,6 @@ - + diff --git a/frontend/package-lock.json b/frontend/package-lock.json index 75175686..a40071dc 100644 --- a/frontend/package-lock.json +++ b/frontend/package-lock.json @@ -33,6 +33,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.12.3", + "@typescript-eslint/eslint-plugin": "^6.6.0", "@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-vue": "^4.3.3", "@vue/eslint-config-prettier": "^8.0.0", @@ -2488,11 +2489,312 @@ "integrity": "sha512-LG4opVs2ANWZ1TJoKc937iMmNstM/d0ae1vNbnBvBhqCSezgVUOzcLCqbI5elV8Vy6WKwKjaqR+zO9VKirBBCA==", "dev": true }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, "node_modules/@types/web-bluetooth": { "version": "0.0.17", "resolved": "https://registry.npmjs.org/@types/web-bluetooth/-/web-bluetooth-0.0.17.tgz", "integrity": "sha512-4p9vcSmxAayx72yn70joFoL44c9MO/0+iVEBIQXe3v2h2SiAsEIo/G5v6ObFWvNKRFjbrVadNf9LqEEZeQPzdA==" }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", + "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/type-utils": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/eslint-plugin/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", + "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", + "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", + "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", + "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", + "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/typescript-estree/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", + "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/@typescript-eslint/utils/node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", + "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, "node_modules/@vitejs/plugin-legacy": { "version": "4.1.1", "resolved": "https://registry.npmjs.org/@vitejs/plugin-legacy/-/plugin-legacy-4.1.1.tgz", @@ -2835,6 +3137,15 @@ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", "dev": true }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, "node_modules/asynckit": { "version": "0.4.0", "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", @@ -3367,6 +3678,18 @@ "resolved": "https://registry.npmjs.org/delegate/-/delegate-3.2.0.tgz", "integrity": "sha512-IofjkYBZaZivn0V8nnsMJGBr4jVLxHDheKSW88PyxS5QC4Vo9ZbZVvhzlSxY87fVq3STR6r+4cGepyHkcWOQSw==" }, + "node_modules/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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, "node_modules/doctrine": { "version": "3.0.0", "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", @@ -4131,6 +4454,26 @@ "node": ">=4" } }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, "node_modules/good-listener": { "version": "1.2.2", "resolved": "https://registry.npmjs.org/good-listener/-/good-listener-1.2.2.tgz", @@ -5083,6 +5426,15 @@ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", "dev": true }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, "node_modules/pathe": { "version": "1.1.1", "resolved": "https://registry.npmjs.org/pathe/-/pathe-1.1.1.tgz", @@ -5672,6 +6024,15 @@ "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==" }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, "node_modules/source-map": { "version": "0.6.1", "resolved": "https://registry.npmjs.org/source-map/-/source-map-0.6.1.tgz", @@ -5875,6 +6236,18 @@ "node": ">=14" } }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, "node_modules/tslib": { "version": "2.6.2", "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.2.tgz", @@ -5930,6 +6303,20 @@ "url": "https://github.com/sponsors/sindresorhus" } }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "devOptional": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, "node_modules/ufo": { "version": "1.3.0", "resolved": "https://registry.npmjs.org/ufo/-/ufo-1.3.0.tgz", diff --git a/frontend/package.json b/frontend/package.json index e0f78bcb..c98508de 100644 --- a/frontend/package.json +++ b/frontend/package.json @@ -9,8 +9,8 @@ "build": "vite build", "watch": "vite build --watch", "clean": "find ./dist -maxdepth 1 -mindepth 1 ! -name '.gitkeep' -exec rm -r {} +", - "lint": "eslint --ext .vue,.js src/", - "lint:fix": "eslint --ext .vue,.js --fix src/", + "lint": "eslint --ext .vue,.ts src/", + "lint:fix": "eslint --ext .vue,.ts --fix src/", "format": "prettier --write ." }, "dependencies": { @@ -39,6 +39,7 @@ }, "devDependencies": { "@intlify/unplugin-vue-i18n": "^0.12.3", + "@typescript-eslint/eslint-plugin": "^6.6.0", "@vitejs/plugin-legacy": "^4.1.1", "@vitejs/plugin-vue": "^4.3.3", "@vue/eslint-config-prettier": "^8.0.0", diff --git a/frontend/public/index.html b/frontend/public/index.html index 39d926d8..04135401 100644 --- a/frontend/public/index.html +++ b/frontend/public/index.html @@ -179,7 +179,7 @@ - + [{[ if .Theme -]}] - - - diff --git a/frontend/src/api/commands.js b/frontend/src/api/commands.ts similarity index 73% rename from frontend/src/api/commands.js rename to frontend/src/api/commands.ts index ffa87dc7..41749470 100644 --- a/frontend/src/api/commands.js +++ b/frontend/src/api/commands.ts @@ -5,13 +5,18 @@ import { useAuthStore } from "@/stores/auth"; const ssl = window.location.protocol === "https:"; const protocol = ssl ? "wss:" : "ws:"; -export default function command(url, command, onmessage, onclose) { +export default function command( + url: string, + command: string, + onmessage: WebSocket["onmessage"], + onclose: WebSocket["onclose"] +) { const authStore = useAuthStore(); url = removePrefix(url); url = `${protocol}//${window.location.host}${baseURL}/api/command${url}?auth=${authStore.jwt}`; - let conn = new window.WebSocket(url); + const conn = new window.WebSocket(url); conn.onopen = () => conn.send(command); conn.onmessage = onmessage; conn.onclose = onclose; diff --git a/frontend/src/api/files.js b/frontend/src/api/files.ts similarity index 72% rename from frontend/src/api/files.js rename to frontend/src/api/files.ts index 65a61e6a..5101a802 100644 --- a/frontend/src/api/files.js +++ b/frontend/src/api/files.ts @@ -3,17 +3,18 @@ import { baseURL } from "@/utils/constants"; import { useAuthStore } from "@/stores/auth"; import { upload as postTus, useTus } from "./tus"; -export async function fetch(url) { +export async function fetch(url: ApiUrl) { url = removePrefix(url); const res = await fetchURL(`/api/resources${url}`, {}); - let data = await res.json(); + const data = await res.json(); data.url = `/files${url}`; if (data.isDir) { if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { + // Perhaps change the any + data.items = data.items.map((item: any, index: any) => { item.index = index; item.url = `${data.url}${encodeURIComponent(item.name)}`; @@ -28,10 +29,12 @@ export async function fetch(url) { return data; } -async function resourceAction(url, method, content) { +async function resourceAction(url: ApiUrl, method: ApiMethod, content?: any) { url = removePrefix(url); - let opts = { method }; + const opts: ApiOpts = { + method, + }; if (content) { opts.body = content; @@ -42,15 +45,15 @@ async function resourceAction(url, method, content) { return res; } -export async function remove(url) { +export async function remove(url: ApiUrl) { return resourceAction(url, "DELETE"); } -export async function put(url, content = "") { +export async function put(url: ApiUrl, content = "") { return resourceAction(url, "PUT", content); } -export function download(format, ...files) { +export function download(format: any, ...files: string[]) { let url = `${baseURL}/api/raw`; if (files.length === 1) { @@ -58,7 +61,7 @@ export function download(format, ...files) { } else { let arg = ""; - for (let file of files) { + for (const file of files) { arg += removePrefix(file) + ","; } @@ -79,7 +82,12 @@ export function download(format, ...files) { window.open(url); } -export async function post(url, content = "", overwrite = false, onupload) { +export async function post( + url: ApiUrl, + content: ApiContent = "", + overwrite = false, + onupload: any = () => {} +) { // Use the pre-existing API if: const useResourcesApi = // a folder is being created @@ -94,10 +102,15 @@ export async function post(url, content = "", overwrite = false, onupload) { : postTus(url, content, overwrite, onupload); } -async function postResources(url, content = "", overwrite = false, onupload) { +async function postResources( + url: ApiUrl, + content: ApiContent = "", + overwrite = false, + onupload: any +) { url = removePrefix(url); - let bufferContent; + let bufferContent: ArrayBuffer; if ( content instanceof Blob && !["http:", "https:"].includes(window.location.protocol) @@ -107,7 +120,7 @@ async function postResources(url, content = "", overwrite = false, onupload) { const authStore = useAuthStore(); return new Promise((resolve, reject) => { - let request = new XMLHttpRequest(); + const request = new XMLHttpRequest(); request.open( "POST", `${baseURL}/api/resources${url}?override=${overwrite}`, @@ -137,12 +150,17 @@ async function postResources(url, content = "", overwrite = false, onupload) { }); } -function moveCopy(items, copy = false, overwrite = false, rename = false) { - let promises = []; +function moveCopy( + items: any[], + copy = false, + overwrite = false, + rename = false +) { + const promises = []; - for (let item of items) { + for (const item of items) { const from = item.from; - const to = encodeURIComponent(removePrefix(item.to)); + const to = encodeURIComponent(removePrefix(item.to ?? "")); const url = `${from}?action=${ copy ? "copy" : "rename" }&destination=${to}&override=${overwrite}&rename=${rename}`; @@ -152,20 +170,20 @@ function moveCopy(items, copy = false, overwrite = false, rename = false) { return Promise.all(promises); } -export function move(items, overwrite = false, rename = false) { +export function move(items: any[], overwrite = false, rename = false) { return moveCopy(items, false, overwrite, rename); } -export function copy(items, overwrite = false, rename = false) { +export function copy(items: any[], overwrite = false, rename = false) { return moveCopy(items, true, overwrite, rename); } -export async function checksum(url, algo) { +export async function checksum(url: ApiUrl, algo: algo) { const data = await resourceAction(`${url}?checksum=${algo}`, "GET"); return (await data.json()).checksums[algo]; } -export function getDownloadURL(file, inline) { +export function getDownloadURL(file: IFile, inline: any) { const params = { ...(inline && { inline: "true" }), }; @@ -173,7 +191,7 @@ export function getDownloadURL(file, inline) { return createURL("api/raw" + file.path, params); } -export function getPreviewURL(file, size) { +export function getPreviewURL(file: IFile, size: string) { const params = { inline: "true", key: Date.parse(file.modified), @@ -182,7 +200,7 @@ export function getPreviewURL(file, size) { return createURL("api/preview/" + size + file.path, params); } -export function getSubtitlesURL(file) { +export function getSubtitlesURL(file: IFile) { const params = { inline: "true", }; @@ -195,7 +213,7 @@ export function getSubtitlesURL(file) { return subtitles; } -export async function usage(url) { +export async function usage(url: ApiUrl) { url = removePrefix(url); const res = await fetchURL(`/api/usage${url}`, {}); diff --git a/frontend/src/api/index.js b/frontend/src/api/index.ts similarity index 100% rename from frontend/src/api/index.js rename to frontend/src/api/index.ts diff --git a/frontend/src/api/pub.js b/frontend/src/api/pub.ts similarity index 76% rename from frontend/src/api/pub.js rename to frontend/src/api/pub.ts index 1511143d..022ddbac 100644 --- a/frontend/src/api/pub.js +++ b/frontend/src/api/pub.ts @@ -1,7 +1,7 @@ import { fetchURL, removePrefix, createURL } from "./utils"; import { baseURL } from "@/utils/constants"; -export async function fetch(url, password = "") { +export async function fetch(url: ApiUrl, password: string = "") { url = removePrefix(url); const res = await fetchURL( @@ -12,12 +12,12 @@ export async function fetch(url, password = "") { false ); - let data = await res.json(); + const data = await res.json(); data.url = `/share${url}`; if (data.isDir) { if (!data.url.endsWith("/")) data.url += "/"; - data.items = data.items.map((item, index) => { + data.items = data.items.map((item: any, index: any) => { item.index = index; item.url = `${data.url}${encodeURIComponent(item.name)}`; @@ -32,7 +32,13 @@ export async function fetch(url, password = "") { return data; } -export function download(format, hash, token, ...files) { +// Is this redundant code? +export function download( + format: any, + hash: string, + token: string, + ...files: any +) { let url = `${baseURL}/api/public/dl/${hash}`; if (files.length === 1) { @@ -40,7 +46,7 @@ export function download(format, hash, token, ...files) { } else { let arg = ""; - for (let file of files) { + for (const file of files) { arg += encodeURIComponent(file) + ","; } @@ -60,7 +66,7 @@ export function download(format, hash, token, ...files) { window.open(url); } -export function getDownloadURL(share, inline = false) { +export function getDownloadURL(share: IFile, inline = false) { const params = { ...(inline && { inline: "true" }), ...(share.token && { token: share.token }), diff --git a/frontend/src/api/search.js b/frontend/src/api/search.ts similarity index 68% rename from frontend/src/api/search.js rename to frontend/src/api/search.ts index 42846880..3169cae3 100644 --- a/frontend/src/api/search.js +++ b/frontend/src/api/search.ts @@ -1,7 +1,7 @@ import { fetchURL, removePrefix } from "./utils"; import url from "../utils/url"; -export default async function search(base, query) { +export default async function search(base: apiUrl, query: string) { base = removePrefix(base); query = encodeURIComponent(query); @@ -9,11 +9,11 @@ export default async function search(base, query) { base += "/"; } - let res = await fetchURL(`/api/search${base}?query=${query}`, {}); + const res = await fetchURL(`/api/search${base}?query=${query}`, {}); let data = await res.json(); - data = data.map((item) => { + data = data.map((item: item) => { item.url = `/files${base}` + url.encodePath(item.path); if (item.dir) { diff --git a/frontend/src/api/settings.js b/frontend/src/api/settings.ts similarity index 80% rename from frontend/src/api/settings.js rename to frontend/src/api/settings.ts index e03b0db1..d17c9146 100644 --- a/frontend/src/api/settings.js +++ b/frontend/src/api/settings.ts @@ -4,7 +4,7 @@ export function get() { return fetchJSON(`/api/settings`, {}); } -export async function update(settings) { +export async function update(settings: ISettings) { await fetchURL(`/api/settings`, { method: "PUT", body: JSON.stringify(settings), diff --git a/frontend/src/api/share.js b/frontend/src/api/share.ts similarity index 77% rename from frontend/src/api/share.js rename to frontend/src/api/share.ts index 28d550ea..553c9228 100644 --- a/frontend/src/api/share.js +++ b/frontend/src/api/share.ts @@ -4,18 +4,23 @@ export async function list() { return fetchJSON("/api/shares"); } -export async function get(url) { +export async function get(url: apiUrl) { url = removePrefix(url); return fetchJSON(`/api/share${url}`); } -export async function remove(hash) { +export async function remove(hash: string) { await fetchURL(`/api/share/${hash}`, { method: "DELETE", }); } -export async function create(url, password = "", expires = "", unit = "hours") { +export async function create( + url: apiUrl, + password = "", + expires = "", + unit = "hours" +) { url = removePrefix(url); url = `/api/share${url}`; if (expires !== "") { @@ -35,6 +40,6 @@ export async function create(url, password = "", expires = "", unit = "hours") { }); } -export function getShareURL(share) { +export function getShareURL(share: share) { return createURL("share/" + share.hash, {}, false); } diff --git a/frontend/src/api/tus.js b/frontend/src/api/tus.ts similarity index 78% rename from frontend/src/api/tus.js rename to frontend/src/api/tus.ts index 83cb607a..d6f85753 100644 --- a/frontend/src/api/tus.js +++ b/frontend/src/api/tus.ts @@ -8,10 +8,10 @@ const RETRY_BASE_DELAY = 1000; const RETRY_MAX_DELAY = 20000; export async function upload( - filePath, - content = "", + filePath: string, + content: ApiContent = "", overwrite = false, - onupload + onupload: any ) { if (!tusSettings) { // Shouldn't happen as we check for tus support before calling this function @@ -19,13 +19,18 @@ export async function upload( } filePath = removePrefix(filePath); - let resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; + const resourcePath = `${tusEndpoint}${filePath}?override=${overwrite}`; await createUpload(resourcePath); const authStore = useAuthStore(); - return new Promise((resolve, reject) => { - let upload = new tus.Upload(content, { + + // Exit early because of typescript, tus content can't be a string + if (content === "") { + return false; + } + return new Promise((resolve, reject) => { + const upload = new tus.Upload(content, { uploadUrl: `${baseURL}${resourcePath}`, chunkSize: tusSettings.chunkSize, retryDelays: computeRetryDelays(tusSettings), @@ -52,8 +57,8 @@ export async function upload( }); } -async function createUpload(resourcePath) { - let headResp = await fetchURL(resourcePath, { +async function createUpload(resourcePath: resourcePath) { + const headResp = await fetchURL(resourcePath, { method: "POST", }); if (headResp.status !== 201) { @@ -63,10 +68,10 @@ async function createUpload(resourcePath) { } } -function computeRetryDelays(tusSettings) { +function computeRetryDelays(tusSettings: tusSettings): number[] | undefined { if (!tusSettings.retryCount || tusSettings.retryCount < 1) { // Disable retries altogether - return null; + return undefined; } // The tus client expects our retries as an array with computed backoffs // E.g.: [0, 3000, 5000, 10000, 20000] @@ -82,7 +87,7 @@ function computeRetryDelays(tusSettings) { return retryDelays; } -export async function useTus(content) { +export async function useTus(content: ApiContent) { return isTusSupported() && content instanceof Blob; } diff --git a/frontend/src/api/users.js b/frontend/src/api/users.ts similarity index 77% rename from frontend/src/api/users.js rename to frontend/src/api/users.ts index 105d6cc0..0acfaad5 100644 --- a/frontend/src/api/users.js +++ b/frontend/src/api/users.ts @@ -4,11 +4,11 @@ export async function getAll() { return fetchJSON(`/api/users`, {}); } -export async function get(id) { +export async function get(id: number) { return fetchJSON(`/api/users/${id}`, {}); } -export async function create(user) { +export async function create(user: user) { const res = await fetchURL(`/api/users`, { method: "POST", body: JSON.stringify({ @@ -23,7 +23,7 @@ export async function create(user) { } } -export async function update(user, which = ["all"]) { +export async function update(user: user, which = ["all"]) { await fetchURL(`/api/users/${user.id}`, { method: "PUT", body: JSON.stringify({ @@ -34,7 +34,7 @@ export async function update(user, which = ["all"]) { }); } -export async function remove(id) { +export async function remove(id: number) { await fetchURL(`/api/users/${id}`, { method: "DELETE", }); diff --git a/frontend/src/api/utils.js b/frontend/src/api/utils.ts similarity index 76% rename from frontend/src/api/utils.js rename to frontend/src/api/utils.ts index 26b62d4f..49da4516 100644 --- a/frontend/src/api/utils.js +++ b/frontend/src/api/utils.ts @@ -3,13 +3,13 @@ import { renew, logout } from "@/utils/auth"; import { baseURL } from "@/utils/constants"; import { encodePath } from "@/utils/url"; -export async function fetchURL(url, opts, auth = true) { +export async function fetchURL(url: ApiUrl, opts: ApiOpts, auth = true) { const authStore = useAuthStore(); opts = opts || {}; opts.headers = opts.headers || {}; - let { headers, ...rest } = opts; + const { headers, ...rest } = opts; let res; try { res = await fetch(`${baseURL}${url}`, { @@ -21,6 +21,7 @@ export async function fetchURL(url, opts, auth = true) { }); } catch { const error = new Error("000 No connection"); + // @ts-ignore don't know yet how to solve error.status = 0; throw error; @@ -32,6 +33,7 @@ export async function fetchURL(url, opts, auth = true) { if (res.status < 200 || res.status > 299) { const error = new Error(await res.text()); + // @ts-ignore don't know yet how to solve error.status = res.status; if (auth && res.status == 401) { @@ -44,17 +46,17 @@ export async function fetchURL(url, opts, auth = true) { return res; } -export async function fetchJSON(url, opts) { +export async function fetchJSON(url: ApiUrl, opts?: any) { const res = await fetchURL(url, opts); if (res.status === 200) { return res.json(); } else { - throw new Error(res.status); + throw new Error(res.status.toString()); } } -export function removePrefix(url) { +export function removePrefix(url: ApiUrl) { url = url.split("/").splice(2).join("/"); if (url === "") url = "/"; @@ -62,7 +64,7 @@ export function removePrefix(url) { return url; } -export function createURL(endpoint, params = {}, auth = true) { +export function createURL(endpoint: ApiUrl, params = {}, auth = true) { const authStore = useAuthStore(); let prefix = baseURL; @@ -71,7 +73,7 @@ export function createURL(endpoint, params = {}, auth = true) { } const url = new URL(prefix + encodePath(endpoint), origin); - const searchParams = { + const searchParams: searchParams = { ...(auth && { auth: authStore.jwt }), ...params, }; diff --git a/frontend/src/index.d.ts b/frontend/src/index.d.ts new file mode 100644 index 00000000..11a8c6e2 --- /dev/null +++ b/frontend/src/index.d.ts @@ -0,0 +1 @@ +declare module "*.vue"; diff --git a/frontend/src/main.js b/frontend/src/main.ts similarity index 98% rename from frontend/src/main.js rename to frontend/src/main.ts index 02e9bf07..923f1dde 100644 --- a/frontend/src/main.js +++ b/frontend/src/main.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { disableExternal } from "@/utils/constants"; import { createApp } from "vue"; import VueLazyload from "vue-lazyload"; @@ -12,6 +13,8 @@ import dayjs from "dayjs"; import localizedFormat from "dayjs/plugin/localizedFormat"; import relativeTime from "dayjs/plugin/relativeTime"; import duration from "dayjs/plugin/duration"; +import "./css/styles.css"; + // register dayjs plugins globally dayjs.extend(localizedFormat); dayjs.extend(relativeTime); diff --git a/frontend/src/router/index.js b/frontend/src/router/index.ts similarity index 89% rename from frontend/src/router/index.js rename to frontend/src/router/index.ts index 31035b49..104e896f 100644 --- a/frontend/src/router/index.js +++ b/frontend/src/router/index.ts @@ -1,4 +1,4 @@ -import { createRouter, createWebHistory } from "vue-router"; +import { RouteLocation, createRouter, createWebHistory } from "vue-router"; import Login from "@/views/Login.vue"; import Layout from "@/views/Layout.vue"; import Files from "@/views/Files.vue"; @@ -144,7 +144,8 @@ const routes = [ }, { path: "/:catchAll(.*)*", - redirect: (to) => `/files/${[...to.params.catchAll].join("/")}`, + redirect: (to: RouteLocation) => + `/files/${[...to.params.catchAll].join("/")}`, }, ]; @@ -156,7 +157,7 @@ async function initAuth() { } if (recaptcha) { - await new Promise((resolve) => { + await new Promise((resolve) => { const check = () => { if (typeof window.grecaptcha === "undefined") { setTimeout(check, 100); @@ -175,10 +176,11 @@ const router = createRouter({ routes, }); -router.beforeResolve(async (to, from, next) => { +router.beforeResolve(async (to: RouteLocation, from, next) => { let title; try { // this should not fail after we finished the migration + // @ts-ignore title = i18n.global.t(titles[to.name]); } catch (error) { console.error(error); @@ -187,14 +189,14 @@ router.beforeResolve(async (to, from, next) => { document.title = title + " - " + name; /*** RTL related settings per route ****/ - const rtlSet = document.querySelector("body").classList.contains("rtl"); + const rtlSet = document.querySelector("body")?.classList.contains("rtl"); const shouldSetRtl = rtlLanguages.includes(i18n.global.locale); switch (true) { case shouldSetRtl && !rtlSet: - document.querySelector("body").classList.add("rtl"); + document.querySelector("body")?.classList.add("rtl"); break; case !shouldSetRtl && rtlSet: - document.querySelector("body").classList.remove("rtl"); + document.querySelector("body")?.classList.remove("rtl"); break; } @@ -225,7 +227,7 @@ router.beforeResolve(async (to, from, next) => { } if (to.matched.some((record) => record.meta.requiresAdmin)) { - if (!authStore.user.perm.admin) { + if (authStore.user === null || !authStore.user.perm.admin) { next({ path: "/403" }); return; } diff --git a/frontend/src/stores/auth.js b/frontend/src/stores/auth.ts similarity index 76% rename from frontend/src/stores/auth.js rename to frontend/src/stores/auth.ts index 7b57ef27..f7cdf159 100644 --- a/frontend/src/stores/auth.js +++ b/frontend/src/stores/auth.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { defineStore } from "pinia"; import dayjs from "dayjs"; import i18n, { detectLocale } from "@/i18n"; @@ -5,7 +6,10 @@ import { cloneDeep } from "lodash-es"; export const useAuthStore = defineStore("auth", { // convert to a function - state: () => ({ + state: (): { + user: user | null; + jwt: string; + } => ({ user: null, jwt: "", }), @@ -15,7 +19,7 @@ export const useAuthStore = defineStore("auth", { }, actions: { // no context as first argument, use `this` instead - setUser(value) { + setUser(value: user) { if (value === null) { this.user = null; return; @@ -23,19 +27,23 @@ export const useAuthStore = defineStore("auth", { const locale = value.locale || detectLocale(); dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; this.user = value; }, - updateUser(value) { + updateUser(value: user) { if (typeof value !== "object") return; - for (let field in value) { + let field: userKey; + for (field in value) { if (field === "locale") { const locale = value[field]; dayjs.locale(locale); + // @ts-ignore Don't know how to fix this yet i18n.global.locale.value = locale; } + // @ts-ignore to fix this.user[field] = cloneDeep(value[field]); } }, diff --git a/frontend/src/stores/clipboard.js b/frontend/src/stores/clipboard.ts similarity index 94% rename from frontend/src/stores/clipboard.js rename to frontend/src/stores/clipboard.ts index 4b016d9b..23eb54b2 100644 --- a/frontend/src/stores/clipboard.js +++ b/frontend/src/stores/clipboard.ts @@ -12,7 +12,7 @@ export const useClipboardStore = defineStore("clipboard", { }, actions: { // no context as first argument, use `this` instead - updateClipboard(value) { + updateClipboard(value: any) { this.key = value.key; this.items = value.items; this.path = value.path; diff --git a/frontend/src/stores/file.js b/frontend/src/stores/file.ts similarity index 72% rename from frontend/src/stores/file.js rename to frontend/src/stores/file.ts index 893f6948..a1138c38 100644 --- a/frontend/src/stores/file.js +++ b/frontend/src/stores/file.ts @@ -2,9 +2,16 @@ import { defineStore } from "pinia"; export const useFileStore = defineStore("file", { // convert to a function - state: () => ({ - req: {}, - oldReq: {}, + state: (): { + req: IFile | null; + oldReq: IFile | null; + reload: boolean; + selected: any[]; + multiple: boolean; + isFiles: boolean; + } => ({ + req: null, + oldReq: null, reload: false, selected: [], multiple: false, @@ -21,7 +28,7 @@ export const useFileStore = defineStore("file", { // return !layoutStore.loading && state.route._value.name === "Files"; // }, isListing: (state) => { - return state.isFiles && state.req.isDir; + return state.isFiles && state?.req?.isDir; }, }, actions: { @@ -29,12 +36,12 @@ export const useFileStore = defineStore("file", { toggleMultiple() { this.multiple = !this.multiple; }, - updateRequest(value) { + updateRequest(value: IFile | null) { this.oldReq = this.req; this.req = value; }, - removeSelected(value) { - let i = this.selected.indexOf(value); + removeSelected(value: any) { + const i = this.selected.indexOf(value); if (i === -1) return; this.selected.splice(i, 1); }, diff --git a/frontend/src/stores/index.js b/frontend/src/stores/index.ts similarity index 69% rename from frontend/src/stores/index.js rename to frontend/src/stores/index.ts index 3ab005bc..7f52aa28 100644 --- a/frontend/src/stores/index.js +++ b/frontend/src/stores/index.ts @@ -1,7 +1,8 @@ import { createPinia as _createPinia } from "pinia"; import { markRaw } from "vue"; +import { Router } from "vue-router"; -export default function createPinia(router) { +export default function createPinia(router: Router) { const pinia = _createPinia(); pinia.use(({ store }) => { store.router = markRaw(router); diff --git a/frontend/src/stores/layout.js b/frontend/src/stores/layout.ts similarity index 82% rename from frontend/src/stores/layout.js rename to frontend/src/stores/layout.ts index 267a27e9..b3d7fc96 100644 --- a/frontend/src/stores/layout.js +++ b/frontend/src/stores/layout.ts @@ -4,7 +4,13 @@ import { defineStore } from "pinia"; export const useLayoutStore = defineStore("layout", { // convert to a function - state: () => ({ + state: (): { + loading: boolean; + show: string | null | boolean; + showConfirm: any; + showAction: boolean | null; + showShell: boolean | null; + } => ({ loading: false, show: null, showConfirm: null, @@ -19,7 +25,7 @@ export const useLayoutStore = defineStore("layout", { toggleShell() { this.showShell = !this.showShell; }, - showHover(value) { + showHover(value: LayoutValue | string) { if (typeof value !== "object") { this.show = value; return; @@ -33,6 +39,7 @@ export const useLayoutStore = defineStore("layout", { }, showError() { this.show = "error"; + console.error(' error') }, showSuccess() { this.show = "success"; diff --git a/frontend/src/stores/router.js b/frontend/src/stores/router.ts similarity index 100% rename from frontend/src/stores/router.js rename to frontend/src/stores/router.ts diff --git a/frontend/src/stores/upload.js b/frontend/src/stores/upload.ts similarity index 80% rename from frontend/src/stores/upload.js rename to frontend/src/stores/upload.ts index f09773eb..28006240 100644 --- a/frontend/src/stores/upload.js +++ b/frontend/src/stores/upload.ts @@ -1,3 +1,4 @@ +// @ts-nocheck import { defineStore } from "pinia"; import { useFileStore } from "./file"; import { files as api } from "@/api"; @@ -6,14 +7,22 @@ import buttons from "@/utils/buttons"; const UPLOADS_LIMIT = 5; -const beforeUnload = (event) => { +const beforeUnload = (event: Event) => { event.preventDefault(); - event.returnValue = ""; + // To remove >> is deprecated + // event.returnValue = ""; }; export const useUploadStore = defineStore("upload", { // convert to a function - state: () => ({ + state: (): { + id: number; + sizes: any[]; + progress: any[]; + queue: any[]; + uploads: uploads; + error: any; + } => ({ id: 0, sizes: [], progress: [], @@ -30,7 +39,8 @@ export const useUploadStore = defineStore("upload", { const totalSize = state.sizes.reduce((a, b) => a + b, 0); - const sum = state.progress.reduce((acc, val) => acc + val); + // @ts-ignore + const sum: number = state.progress.reduce((acc, val) => acc + val); return Math.ceil((sum / totalSize) * 100); }, filesInUploadCount: (state) => { @@ -40,7 +50,7 @@ export const useUploadStore = defineStore("upload", { filesInUpload: (state) => { const files = []; - for (let index in state.uploads) { + for (const index in state.uploads) { const upload = state.uploads[index]; const id = upload.id; const type = upload.type; @@ -65,8 +75,9 @@ export const useUploadStore = defineStore("upload", { }, actions: { // no context as first argument, use `this` instead - setProgress({ id, loaded }) { + setProgress(obj: { id: number; loaded: boolean }) { // Vue.set(this.progress, id, loaded); + const { id, loaded } = obj; this.progress[id] = loaded; }, setError(error) { @@ -77,7 +88,7 @@ export const useUploadStore = defineStore("upload", { this.sizes = []; this.progress = []; }, - addJob(item) { + addJob(item: item) { this.queue.push(item); this.sizes[this.id] = item.file.size; this.id++; @@ -88,15 +99,15 @@ export const useUploadStore = defineStore("upload", { // Vue.set(this.uploads, item.id, item); this.uploads[item.id] = item; }, - removeJob(id) { + removeJob(id: number) { // Vue.delete(this.uploads, id); delete this.uploads[id]; }, - upload(item) { - let uploadsCount = Object.keys(this.uploads).length; + upload(item: item) { + const uploadsCount = Object.keys(this.uploads).length; - let isQueueEmpty = this.queue.length == 0; - let isUploadsEmpty = uploadsCount == 0; + const isQueueEmpty = this.queue.length == 0; + const isUploadsEmpty = uploadsCount == 0; if (isQueueEmpty && isUploadsEmpty) { window.addEventListener("beforeunload", beforeUnload); @@ -106,8 +117,8 @@ export const useUploadStore = defineStore("upload", { this.addJob(item); this.processUploads(); }, - finishUpload(item) { - this.setProgress({ id: item.id, loaded: item.file.size }); + finishUpload(item: item) { + this.setProgress({ id: item.id, loaded: item.file.size > 0 }); this.removeJob(item.id); this.processUploads(); }, @@ -136,7 +147,7 @@ export const useUploadStore = defineStore("upload", { if (item.file.isDir) { await api.post(item.path).catch(this.setError); } else { - let onUpload = throttle( + const onUpload = throttle( (event) => this.setProgress({ id: item.id, diff --git a/frontend/src/types/api.d.ts b/frontend/src/types/api.d.ts new file mode 100644 index 00000000..02251bcc --- /dev/null +++ b/frontend/src/types/api.d.ts @@ -0,0 +1,40 @@ +type ApiUrl = string; // Can also be set as a path eg: "path1" | "path2" + +type resourcePath = string; + +type ApiMethod = "GET" | "POST" | "PUT" | "DELETE" | "PATCH"; + +type ApiContent = + | Blob + | File + | Pick, "read"> + | ""; + +interface ApiOpts { + method?: ApiMethod; + headers?: object; + body?: any; +} + +interface tusSettings { + retryCount: number; + chunkSize: number; +} + +type algo = any; + +type inline = any; + +interface share { + expire: any; + hash: string; + path: string; + userID: number; + token: string; +} + +interface settings { + any; +} + +type searchParams = any; diff --git a/frontend/src/types/file.d.ts b/frontend/src/types/file.d.ts new file mode 100644 index 00000000..b21ef587 --- /dev/null +++ b/frontend/src/types/file.d.ts @@ -0,0 +1,48 @@ +interface IFile { + index?: number; + name: string; + modified: string; + path: string; + subtitles: any[]; + isDir: boolean; + size: number; + fullPath: string; + type: uploadType; + items: IFile[]; + token?: string; + hash: string; + url?: string; +} + +type uploadType = + | "video" + | "audio" + | "image" + | "pdf" + | "text" + | "blob" + | "textImmutable"; + +type req = { + path: string; + name: string; + size: number; + extension: string; + modified: string; + mode: number; + isDir: boolean; + isSymlink: boolean; + type: string; + url: string; + hash: string; +}; + +interface uploads { + [key: string]: upload; +} + +interface upload { + id: number; + file: file; + type: string; +} diff --git a/frontend/src/types/global.d.ts b/frontend/src/types/global.d.ts new file mode 100644 index 00000000..aca5fc0f --- /dev/null +++ b/frontend/src/types/global.d.ts @@ -0,0 +1,8 @@ +export {}; + +declare global { + interface Window { + FileBrowser: any; + grecaptcha: any; + } +} diff --git a/frontend/src/types/layout.d.ts b/frontend/src/types/layout.d.ts new file mode 100644 index 00000000..941a18f3 --- /dev/null +++ b/frontend/src/types/layout.d.ts @@ -0,0 +1,5 @@ +interface LayoutValue { + prompt: string; + confirm: any; + action?: boolean; +} diff --git a/frontend/src/types/settings.d.ts b/frontend/src/types/settings.d.ts new file mode 100644 index 00000000..ceed011d --- /dev/null +++ b/frontend/src/types/settings.d.ts @@ -0,0 +1,73 @@ +interface ISettings { + signup: boolean; + createUserDir: boolean; + userHomeBasePath: string; + defaults: Defaults; + rules: any[]; + branding: SettingsBranding; + tus: SettingsTus; + shell: string[]; + commands: SettingsCommand; +} + +interface SettingsDefaults { + scope: string; + locale: string; + viewMode: string; + singleClick: boolean; + sorting: SettingsSorting; + perm: SettingsPerm; + commands: any[]; + hideDotfiles: boolean; + dateFormat: boolean; +} + +interface SettingsSorting { + by: string; + asc: boolean; +} + +interface SettingsPerm { + admin: boolean; + execute: boolean; + create: boolean; + rename: boolean; + modify: boolean; + delete: boolean; + share: boolean; + download: boolean; +} + +interface SettingsBranding { + name: string; + disableExternal: boolean; + disableUsedPercentage: boolean; + files: string; + theme: string; + color: string; +} + +interface SettingsTus { + chunkSize: number; + retryCount: number; +} + +interface SettingsCommand { + after_copy?: string[]; + after_delete?: string[]; + after_rename?: string[]; + after_save?: string[]; + after_upload?: string[]; + before_copy?: string[]; + before_delete?: string[]; + before_rename?: string[]; + before_save?: string[]; + before_upload?: string[]; +} + +interface SettingsUnit { + KB: number; + MB: number; + GB: number; + TB: number; +} diff --git a/frontend/src/types/toast.d.ts b/frontend/src/types/toast.d.ts new file mode 100644 index 00000000..dc11090c --- /dev/null +++ b/frontend/src/types/toast.d.ts @@ -0,0 +1 @@ +type TToast = (message: string) => void; diff --git a/frontend/src/types/user.d.ts b/frontend/src/types/user.d.ts new file mode 100644 index 00000000..73596e64 --- /dev/null +++ b/frontend/src/types/user.d.ts @@ -0,0 +1,7 @@ +interface user { + id: number; + locale: string; + perm: any; +} + +type userKey = keyof user; diff --git a/frontend/src/types/utils.d.ts b/frontend/src/types/utils.d.ts new file mode 100644 index 00000000..4c77acc1 --- /dev/null +++ b/frontend/src/types/utils.d.ts @@ -0,0 +1 @@ +type settings = any; diff --git a/frontend/src/utils/auth.js b/frontend/src/utils/auth.ts similarity index 78% rename from frontend/src/utils/auth.js rename to frontend/src/utils/auth.ts index 8e5fbcba..b2a02545 100644 --- a/frontend/src/utils/auth.js +++ b/frontend/src/utils/auth.ts @@ -3,9 +3,9 @@ import router from "@/router"; import jwt_decode from "jwt-decode"; import { baseURL } from "./constants"; -export function parseToken(token) { +export function parseToken(token: string) { // falsy or malformed jwt will throw InvalidTokenError - const data = jwt_decode(token); + const data = jwt_decode<{ [key: string]: any; user: user }>(token); document.cookie = `auth=${token}; Path=/; SameSite=Strict;`; @@ -19,7 +19,7 @@ export function parseToken(token) { export async function validateLogin() { try { if (localStorage.getItem("jwt")) { - await renew(localStorage.getItem("jwt")); + await renew(localStorage.getItem("jwt")); } } catch (error) { console.warn("Invalid JWT token in storage"); // eslint-disable-line @@ -27,7 +27,11 @@ export async function validateLogin() { } } -export async function login(username, password, recaptcha) { +export async function login( + username: string, + password: string, + recaptcha: string +) { const data = { username, password, recaptcha }; const res = await fetch(`${baseURL}/api/login`, { @@ -47,7 +51,7 @@ export async function login(username, password, recaptcha) { } } -export async function renew(jwt) { +export async function renew(jwt: string) { const res = await fetch(`${baseURL}/api/renew`, { method: "POST", headers: { @@ -64,7 +68,7 @@ export async function renew(jwt) { } } -export async function signup(username, password) { +export async function signup(username: string, password: string) { const data = { username, password }; const res = await fetch(`${baseURL}/api/signup`, { @@ -76,6 +80,7 @@ export async function signup(username, password) { }); if (res.status !== 200) { + // @ts-ignore still need to fix these errors throw new Error(res.status); } } @@ -86,6 +91,6 @@ export function logout() { const authStore = useAuthStore(); authStore.clearUser(); - localStorage.setItem("jwt", null); + localStorage.setItem("jwt", ""); router.push({ path: "/login" }); } diff --git a/frontend/src/utils/buttons.js b/frontend/src/utils/buttons.js deleted file mode 100644 index 1c6bdeee..00000000 --- a/frontend/src/utils/buttons.js +++ /dev/null @@ -1,70 +0,0 @@ -function loading(button) { - let el = document.querySelector(`#${button}-button > i`); - - if (el === undefined || el === null) { - console.log("Error getting button " + button); // eslint-disable-line - return; - } - - if (el.innerHTML == "autorenew" || el.innerHTML == "done") { - return; - } - - el.dataset.icon = el.innerHTML; - el.style.opacity = 0; - - setTimeout(() => { - el.classList.add("spin"); - el.innerHTML = "autorenew"; - el.style.opacity = 1; - }, 100); -} - -function done(button) { - let el = document.querySelector(`#${button}-button > i`); - - if (el === undefined || el === null) { - console.log("Error getting button " + button); // eslint-disable-line - return; - } - - el.style.opacity = 0; - - setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; - }, 100); -} - -function success(button) { - let el = document.querySelector(`#${button}-button > i`); - - if (el === undefined || el === null) { - console.log("Error getting button " + button); // eslint-disable-line - return; - } - - el.style.opacity = 0; - - setTimeout(() => { - el.classList.remove("spin"); - el.innerHTML = "done"; - el.style.opacity = 1; - - setTimeout(() => { - el.style.opacity = 0; - - setTimeout(() => { - el.innerHTML = el.dataset.icon; - el.style.opacity = 1; - }, 100); - }, 500); - }, 100); -} - -export default { - loading, - done, - success, -}; diff --git a/frontend/src/utils/buttons.ts b/frontend/src/utils/buttons.ts new file mode 100644 index 00000000..6a8ac249 --- /dev/null +++ b/frontend/src/utils/buttons.ts @@ -0,0 +1,83 @@ +function loading(button: string) { + const el: HTMLButtonElement | null = document.querySelector( + `#${button}-button > i` + ); + + if (el === undefined || el === null) { + console.log("Error getting button " + button); // eslint-disable-line + return; + } + + if (el.innerHTML == "autorenew" || el.innerHTML == "done") { + return; + } + + el.dataset.icon = el.innerHTML; + el.style.opacity = "0"; + + setTimeout(() => { + if (el) { + el.classList.add("spin"); + el.innerHTML = "autorenew"; + el.style.opacity = "1"; + } + }, 100); +} + +function done(button: string) { + const el: HTMLButtonElement | null = document.querySelector( + `#${button}-button > i` + ); + + if (el === undefined || el === null) { + console.log("Error getting button " + button); // eslint-disable-line + return; + } + + el.style.opacity = "0"; + + setTimeout(() => { + if (el !== null) { + el.classList.remove("spin"); + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } + }, 100); +} + +function success(button: string) { + const el: HTMLButtonElement | null = document.querySelector( + `#${button}-button > i` + ); + + if (el === undefined || el === null) { + console.log("Error getting button " + button); // eslint-disable-line + return; + } + + el.style.opacity = "0"; + + setTimeout(() => { + if (el !== null) { + el.classList.remove("spin"); + el.innerHTML = "done"; + el.style.opacity = "1"; + } + setTimeout(() => { + if (el) el.style.opacity = "0"; + + setTimeout(() => { + if (el !== null) { + el.innerHTML = el?.dataset?.icon || ""; + el.style.opacity = "1"; + } + }, 100); + }, 500); + }, 100); +} + +export default { + loading, + done, + success, +}; diff --git a/frontend/src/utils/constants.js b/frontend/src/utils/constants.ts similarity index 100% rename from frontend/src/utils/constants.js rename to frontend/src/utils/constants.ts diff --git a/frontend/src/utils/cookie.js b/frontend/src/utils/cookie.ts similarity index 63% rename from frontend/src/utils/cookie.js rename to frontend/src/utils/cookie.ts index 72d59be4..bdebf4f0 100644 --- a/frontend/src/utils/cookie.js +++ b/frontend/src/utils/cookie.ts @@ -1,5 +1,5 @@ -export default function (name) { - let re = new RegExp( +export default function (name: string) { + const re = new RegExp( "(?:(?:^|.*;\\s*)" + name + "\\s*\\=\\s*([^;]*).*$)|^.*$" ); return document.cookie.replace(re, "$1"); diff --git a/frontend/src/utils/css.js b/frontend/src/utils/css.ts similarity index 86% rename from frontend/src/utils/css.js rename to frontend/src/utils/css.ts index 405c0dd2..08855b8c 100644 --- a/frontend/src/utils/css.js +++ b/frontend/src/utils/css.ts @@ -1,10 +1,10 @@ -export default function getRule(rules) { +export default function getRule(rules: any) { for (let i = 0; i < rules.length; i++) { rules[i] = rules[i].toLowerCase(); } let result = null; - let find = Array.prototype.find; + const find = Array.prototype.find; find.call(document.styleSheets, (styleSheet) => { result = find.call(styleSheet.cssRules, (cssRule) => { diff --git a/frontend/src/utils/index.js b/frontend/src/utils/index.js deleted file mode 100644 index 0b6cd571..00000000 --- a/frontend/src/utils/index.js +++ /dev/null @@ -1 +0,0 @@ -export * from "./funcs"; diff --git a/frontend/src/utils/index.ts b/frontend/src/utils/index.ts new file mode 100644 index 00000000..5646c03d --- /dev/null +++ b/frontend/src/utils/index.ts @@ -0,0 +1 @@ +// export * from "./funcs"; diff --git a/frontend/src/utils/upload.js b/frontend/src/utils/upload.ts similarity index 74% rename from frontend/src/utils/upload.js rename to frontend/src/utils/upload.ts index 88a08b7b..3616b28f 100644 --- a/frontend/src/utils/upload.js +++ b/frontend/src/utils/upload.ts @@ -1,26 +1,27 @@ import { useUploadStore } from "@/stores/upload"; import url from "@/utils/url"; -export function checkConflict(files, items) { +export function checkConflict(files: file[], items: item[]) { if (typeof items === "undefined" || items === null) { items = []; } - let folder_upload = files[0].fullPath !== undefined; + const folder_upload = files[0].fullPath !== undefined; let conflict = false; for (let i = 0; i < files.length; i++) { - let file = files[i]; + const file = files[i]; let name = file.name; if (folder_upload) { - let dirs = file.fullPath.split("/"); + const dirs = file.fullPath.split("/"); if (dirs.length > 1) { name = dirs[0]; } } - let res = items.findIndex(function hasConflict(element) { + const res = items.findIndex(function hasConflict(element) { + // @ts-ignore Don't know what this does return element.name === this; }, name); @@ -33,13 +34,13 @@ export function checkConflict(files, items) { return conflict; } -export function scanFiles(dt) { +export function scanFiles(dt: { [key: string]: any; item: item }) { return new Promise((resolve) => { let reading = 0; - const contents = []; + const contents: any[] = []; if (dt.items !== undefined) { - for (let item of dt.items) { + for (const item of dt.items) { if ( item.kind === "file" && typeof item.webkitGetAsEntry === "function" @@ -52,10 +53,10 @@ export function scanFiles(dt) { resolve(dt.files); } - function readEntry(entry, directory = "") { + function readEntry(entry: any, directory = "") { if (entry.isFile) { reading++; - entry.file((file) => { + entry.file((file: file) => { reading--; file.fullPath = `${directory}${file.name}`; @@ -79,10 +80,10 @@ export function scanFiles(dt) { } } - function readReaderContent(reader, directory) { + function readReaderContent(reader: any, directory: string) { reading++; - reader.readEntries(function (entries) { + reader.readEntries(function (entries: any[]) { reading--; if (entries.length > 0) { for (const entry of entries) { @@ -100,7 +101,7 @@ export function scanFiles(dt) { }); } -function detectType(mimetype) { +function detectType(mimetype: string): uploadType { if (mimetype.startsWith("video")) return "video"; if (mimetype.startsWith("audio")) return "audio"; if (mimetype.startsWith("image")) return "image"; @@ -109,13 +110,13 @@ function detectType(mimetype) { return "blob"; } -export function handleFiles(files, base, overwrite = false) { +export function handleFiles(files: file[], base: string, overwrite = false) { const uploadStore = useUploadStore(); for (let i = 0; i < files.length; i++) { - let id = uploadStore.id; + const id = uploadStore.id; let path = base; - let file = files[i]; + const file = files[i]; if (file.fullPath !== undefined) { path += url.encodePath(file.fullPath); diff --git a/frontend/src/utils/url.js b/frontend/src/utils/url.ts similarity index 86% rename from frontend/src/utils/url.js rename to frontend/src/utils/url.ts index bf30a17c..063fa6d2 100644 --- a/frontend/src/utils/url.js +++ b/frontend/src/utils/url.ts @@ -1,5 +1,5 @@ -export function removeLastDir(url) { - var arr = url.split("/"); +export function removeLastDir(url: string) { + const arr = url.split("/"); if (arr.pop() === "") { arr.pop(); } @@ -9,7 +9,7 @@ export function removeLastDir(url) { // this function is taken from mozilla // https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/encodeURIComponent#Examples -export function encodeRFC5987ValueChars(str) { +export function encodeRFC5987ValueChars(str: string) { return ( encodeURIComponent(str) // The following creates the sequences %27 %28 %29 %2A (Note that @@ -28,7 +28,7 @@ export function encodeRFC5987ValueChars(str) { ); } -export function encodePath(str) { +export function encodePath(str: string) { return str .split("/") .map((v) => encodeURIComponent(v)) diff --git a/frontend/src/utils/vue.cjs b/frontend/src/utils/vue.cjs new file mode 100644 index 00000000..d3f2d50b --- /dev/null +++ b/frontend/src/utils/vue.cjs @@ -0,0 +1,67 @@ +import Vue from "vue"; +import Noty from "noty"; +import VueLazyload from "vue-lazyload"; +// @ts-ignore +import i18n from "@/i18n"; +import { disableExternal } from "@/utils/constants"; + +Vue.use(VueLazyload); + +Vue.config.productionTip = true; + +const notyDefault = { + type: "info", + layout: "bottomRight", + timeout: 1000, + progressBar: true, +}; + +Vue.prototype.$noty = (opts) => { + new Noty(Object.assign({}, notyDefault, opts)).show(); +}; + +Vue.prototype.$showSuccess = (message) => { + new Noty( + Object.assign({}, notyDefault, { + text: message, + type: "success", + }) + ).show(); +}; + +Vue.prototype.$showError = (error, displayReport = true) => { + let btns = [ + Noty.button(i18n.t("buttons.close"), "", function () { + n.close(); + }), + ]; + + if (!disableExternal && displayReport) { + btns.unshift( + Noty.button(i18n.t("buttons.reportIssue"), "", function () { + window.open( + "https://github.com/filebrowser/filebrowser/issues/new/choose" + ); + }) + ); + } + + let n = new Noty( + Object.assign({}, notyDefault, { + text: error.message || error, + type: "error", + timeout: null, + buttons: btns, + }) + ); + + n.show(); +}; + +Vue.directive("focus", { + inserted: function (el) { + el.focus(); + }, +}); + +export default Vue; diff --git a/frontend/src/views/Errors.vue b/frontend/src/views/Errors.vue index 4b9c58e8..5ddd9ebe 100644 --- a/frontend/src/views/Errors.vue +++ b/frontend/src/views/Errors.vue @@ -4,15 +4,24 @@

{{ info.icon }} - {{ $t(info.message) }} + {{ t(info.message) }}

- diff --git a/frontend/src/views/Files.vue b/frontend/src/views/Files.vue index 51744265..2d91e962 100644 --- a/frontend/src/views/Files.vue +++ b/frontend/src/views/Files.vue @@ -1,10 +1,14 @@ - diff --git a/frontend/src/views/Layout.vue b/frontend/src/views/Layout.vue index 58bef3f8..c5e62d3f 100644 --- a/frontend/src/views/Layout.vue +++ b/frontend/src/views/Layout.vue @@ -1,56 +1,46 @@ - diff --git a/frontend/src/views/Login.vue b/frontend/src/views/Login.vue index 9b372cbb..4c5ba068 100644 --- a/frontend/src/views/Login.vue +++ b/frontend/src/views/Login.vue @@ -11,39 +11,37 @@ type="text" autocapitalize="off" v-model="username" - :placeholder="$t('login.username')" + :placeholder="t('login.username')" />

- {{ - createMode ? $t("login.loginInstead") : $t("login.createAnAccount") - }} + {{ createMode ? t("login.loginInstead") : t("login.createAnAccount") }}

- diff --git a/frontend/src/views/Settings.vue b/frontend/src/views/Settings.vue index 5f093065..271715b2 100644 --- a/frontend/src/views/Settings.vue +++ b/frontend/src/views/Settings.vue @@ -7,27 +7,27 @@
  • - {{ $t("settings.profileSettings") }} + {{ t("settings.profileSettings") }}
  • -
  • - {{ $t("settings.shareManagement") }} + {{ t("settings.shareManagement") }}
  • -
  • - {{ $t("settings.globalSettings") }} + {{ t("settings.globalSettings") }}
  • -
  • - {{ $t("settings.userManagement") }} + {{ t("settings.userManagement") }}
@@ -41,7 +41,7 @@
- {{ $t("files.loading") }} + {{ t("files.loading") }} @@ -49,20 +49,18 @@ - diff --git a/frontend/src/views/Share.vue b/frontend/src/views/Share.vue index 6434f00a..d021be66 100644 --- a/frontend/src/views/Share.vue +++ b/frontend/src/views/Share.vue @@ -4,55 +4,55 @@ <action - v-if="selectedCount" + v-if="fileStore.selectedCount" icon="file_download" - :label="$t('buttons.download')" + :label="t('buttons.download')" @action="download" - :counter="selectedCount" + :counter="fileStore.selectedCount" /> <button v-if="isSingleFile()" class="action copy-clipboard" :data-clipboard-text="linkSelected()" - :aria-label="$t('buttons.copyDownloadLinkToClipboard')" - :title="$t('buttons.copyDownloadLinkToClipboard')" + :aria-label="t('buttons.copyDownloadLinkToClipboard')" + :data-title="t('buttons.copyDownloadLinkToClipboard')" > <i class="material-icons">content_paste</i> </button> <action icon="check_circle" - :label="$t('buttons.selectMultiple')" + :label="t('buttons.selectMultiple')" @action="toggleMultipleSelection" /> </header-bar> <breadcrumbs :base="'/share/' + hash" /> - <div v-if="loading"> + <div v-if="layoutStore.loading"> <h2 class="message delayed"> <div class="spinner"> <div class="bounce1"></div> <div class="bounce2"></div> <div class="bounce3"></div> </div> - <span>{{ $t("files.loading") }}</span> + <span>{{ t("files.loading") }}</span> </h2> </div> <div v-else-if="error"> <div v-if="error.status === 401"> <div class="card floating" id="password"> <div v-if="attemptedPasswordLogin" class="share__wrong__password"> - {{ $t("login.wrongCredentials") }} + {{ t("login.wrongCredentials") }} </div> <div class="card-title"> - <h2>{{ $t("login.password") }}</h2> + <h2>{{ t("login.password") }}</h2> </div> <div class="card-content"> <input v-focus type="password" - :placeholder="$t('login.password')" + :placeholder="t('login.password')" v-model="password" @keyup.enter="fetchData" /> @@ -61,43 +61,43 @@ <button class="button button--flat" @click="fetchData" - :aria-label="$t('buttons.submit')" - :title="$t('buttons.submit')" + :aria-label="t('buttons.submit')" + :data-title="t('buttons.submit')" > - {{ $t("buttons.submit") }} + {{ t("buttons.submit") }} </button> </div> </div> </div> <errors v-else :errorCode="error.status" /> </div> - <div v-else> + <div v-else-if="req !== null"> <div class="share"> <div class="share__box share__box__info"> <div class="share__box__header"> {{ req.isDir - ? $t("download.downloadFolder") - : $t("download.downloadFile") + ? t("download.downloadFolder") + : t("download.downloadFile") }} </div> <div class="share__box__element share__box__center share__box__icon"> <i class="material-icons">{{ icon }}</i> </div> <div class="share__box__element"> - <strong>{{ $t("prompts.displayName") }}</strong> {{ req.name }} + <strong>{{ t("prompts.displayName") }}</strong> {{ req.name }} </div> - <div class="share__box__element" :title="modTime"> - <strong>{{ $t("prompts.lastModified") }}:</strong> {{ humanTime }} + <div class="share__box__element" :data-title="modTime"> + <strong>{{ t("prompts.lastModified") }}:</strong> {{ humanTime }} </div> <div class="share__box__element"> - <strong>{{ $t("prompts.size") }}:</strong> {{ humanSize }} + <strong>{{ t("prompts.size") }}:</strong> {{ humanSize }} </div> <div class="share__box__element share__box__center"> <a target="_blank" :href="link" class="button button--flat"> <div> <i class="material-icons">file_download</i - >{{ $t("buttons.download") }} + >{{ t("buttons.download") }} </div> </a> <a @@ -108,7 +108,7 @@ > <div> <i class="material-icons">open_in_new</i - >{{ $t("buttons.openFile") }} + >{{ t("buttons.openFile") }} </div> </a> </div> @@ -121,11 +121,11 @@ class="share__box share__box__items" > <div class="share__box__header" v-if="req.isDir"> - {{ $t("files.files") }} + {{ t("files.files") }} </div> <div id="listing" class="list file-icons"> <item - v-for="item in req.items.slice(0, this.showLimit)" + v-for="item in req.items.slice(0, showLimit)" :key="base64(item.name)" v-bind:index="item.index" v-bind:name="item.name" @@ -147,14 +147,17 @@ </div> </div> - <div :class="{ active: multiple }" id="multiple-selection"> - <p>{{ $t("files.multipleSelectionEnabled") }}</p> + <div + :class="{ active: fileStore.multiple }" + id="multiple-selection" + > + <p>{{ t("files.multipleSelectionEnabled") }}</p> <div - @click="() => (multiple = false)" + @click="() => (fileStore.multiple = false)" tabindex="0" role="button" - :title="$t('files.clear')" - :aria-label="$t('files.clear')" + :data-title="t('files.clear')" + :aria-label="t('files.clear')" class="action" > <i class="material-icons">clear</i> @@ -168,7 +171,7 @@ > <h2 class="message"> <i class="material-icons">sentiment_dissatisfied</i> - <span>{{ $t("files.lonely") }}</span> + <span>{{ t("files.lonely") }}</span> </h2> </div> </div> @@ -176,8 +179,7 @@ </div> </template> -<script> -import { mapState, mapActions, mapWritableState } from "pinia"; +<script setup lang="ts"> import { pub as api } from "@/api"; import { filesize } from "filesize"; import dayjs from "dayjs"; @@ -192,171 +194,166 @@ import Item from "@/components/files/ListingItem.vue"; import Clipboard from "clipboard"; import { useFileStore } from "@/stores/file"; import { useLayoutStore } from "@/stores/layout"; +import { computed, onBeforeUnmount, onMounted, ref, watch } from "vue"; +import { useRoute } from "vue-router"; +import { useI18n } from "vue-i18n"; -export default { - name: "share", - components: { - HeaderBar, - Action, - Breadcrumbs, - Item, - QrcodeVue, - Errors, - }, - data: () => ({ - error: null, - showLimit: 100, - password: "", - attemptedPasswordLogin: false, - hash: null, - token: null, - clip: null, - }), - inject: ["$showSuccess"], - watch: { - $route: function () { - this.showLimit = 100; +const error = ref<null | any>(null); +const showLimit = ref<number>(100); +const password = ref<string>(""); +const attemptedPasswordLogin = ref<boolean>(false); +const hash = ref<any>(null); +const token = ref<any>(null); +const clip = ref<any>(null); - this.fetchData(); - }, - }, - created: async function () { - const hash = this.$route.params.path[0]; - this.hash = hash; - await this.fetchData(); - }, - mounted() { - window.addEventListener("keydown", this.keyEvent); - this.clip = new Clipboard(".copy-clipboard"); - this.clip.on("success", () => { - this.$showSuccess(this.$t("success.linkCopied")); - }); - }, - beforeUnmount() { - window.removeEventListener("keydown", this.keyEvent); - this.clip.destroy(); - }, - computed: { - ...mapState(useFileStore, ["req", "selectedCount"]), - ...mapWritableState(useFileStore, ["reload", "multiple", "selected"]), - ...mapWritableState(useLayoutStore, ["loading"]), - icon: function () { - if (this.req.isDir) return "folder"; - if (this.req.type === "image") return "insert_photo"; - if (this.req.type === "audio") return "volume_up"; - if (this.req.type === "video") return "movie"; - return "insert_drive_file"; - }, - link: function () { - return api.getDownloadURL(this.req); - }, - inlineLink: function () { - return api.getDownloadURL(this.req, true); - }, - humanSize: function () { - if (this.req.isDir) { - return this.req.items.length; - } +const { t } = useI18n({}); - return filesize(this.req.size); - }, - humanTime: function () { - return dayjs(this.req.modified).fromNow(); - }, - modTime: function () { - return new Date(Date.parse(this.req.modified)).toLocaleString(); - }, - }, - methods: { - ...mapActions(useFileStore, ["updateRequest", "toggleMultiple"]), - ...mapActions(useLayoutStore, ["showHover", "closeHovers"]), - base64: function (name) { - return Base64.encodeURI(name); - }, - fetchData: async function () { - // Reset view information. - this.reload = false; - this.selected = []; - this.multiple = false; - this.closeHovers(); +const route = useRoute(); +const fileStore = useFileStore(); +const layoutStore = useLayoutStore(); - // Set loading to true and reset the error. - this.loading = true; - this.error = null; +watch(route, () => { + showLimit.value = 100; + fetchData(); +}); - if (this.password !== "") { - this.attemptedPasswordLogin = true; - } +const req = computed(() => fileStore.req); - let url = this.$route.path; - if (url === "") url = "/"; - if (url[0] !== "/") url = "/" + url; +// Define computes - try { - let file = await api.fetch(url, this.password); - file.hash = this.hash; +const icon = computed(() => { + if (req.value === null) return "insert_drive_file"; + if (req.value.isDir) return "folder"; + if (req.value.type === "image") return "insert_photo"; + if (req.value.type === "audio") return "volume_up"; + if (req.value.type === "video") return "movie"; + return "insert_drive_file"; +}); - this.token = file.token || ""; +const link = computed(() => (req.value ? api.getDownloadURL(req.value) : "")); +const inlineLink = computed(() => + req.value ? api.getDownloadURL(req.value, true) : "" +); +const humanSize = computed(() => { + if (req.value) { + return req.value.isDir + ? req.value.items.length + : filesize(req.value.size ?? 0); + } else { + return ""; + } +}); +const humanTime = computed(() => dayjs(req.value?.modified).fromNow()); +const modTime = computed(() => + req.value + ? new Date(Date.parse(req.value.modified)).toLocaleString() + : new Date() +); - this.updateRequest(file); - document.title = `${file.name} - ${document.title}`; - } catch (e) { - this.error = e; - } finally { - this.loading = false; - } - }, - keyEvent(event) { - if (event.key === "Escape") { - // If we're on a listing, unselect all - // files and folders. - if (this.selectedCount > 0) { - this.selected = []; - } - } - }, - toggleMultipleSelection() { - this.toggleMultiple(); - }, - isSingleFile: function () { - return ( - this.selectedCount === 1 && !this.req.items[this.selected[0]].isDir - ); - }, - download() { - if (this.isSingleFile()) { - api.download( - null, - this.hash, - this.token, - this.req.items[this.selected[0]].path - ); - return; - } +// Functions +const base64 = (name: any) => Base64.encodeURI(name); +const fetchData = async () => { + fileStore.reload = false; + fileStore.selected = []; + fileStore.multiple = false; + // fileStore.closeHovers(); - this.showHover({ - prompt: "download", - confirm: (format) => { - this.closeHovers(); + // Set loading to true and reset the error. + layoutStore.loading = true; + error.value = null; + if (password.value !== "") { + attemptedPasswordLogin.value = true; + } - let files = []; + let url = route.path; + if (url === "") url = "/"; + if (url[0] !== "/") url = "/" + url; - for (let i of this.selected) { - files.push(this.req.items[i].path); - } + try { + let file = await api.fetch(url, password.value); + file.hash = hash.value; - api.download(format, this.hash, this.token, ...files); - }, - }); - }, - linkSelected: function () { - return this.isSingleFile() - ? api.getDownloadURL({ - hash: this.hash, - path: this.req.items[this.selected[0]].path, - }) - : ""; - }, - }, + token.value = file.token || ""; + + fileStore.updateRequest(file); + document.title = `${file.name} - ${document.title}`; + } catch (e) { + error.value = e; + } finally { + layoutStore.loading = false; + } }; + +const keyEvent = (event: KeyboardEvent) => { + if (event.key === "Escape") { + // If we're on a listing, unselect all + // files and folders. + if (fileStore.selectedCount > 0) { + fileStore.selected = []; + } + } +}; + +const toggleMultipleSelection = () => { + // toggle +}; + +const isSingleFile = () => + fileStore.selectedCount === 1 && + !req.value?.items[fileStore.selected[0]].isDir; + +const download = () => { + if (isSingleFile()) { + api.download( + null, + hash.value, + token.value, + req.value?.items[fileStore.selected[0]].path + ); + return; + } + layoutStore.showHover({ + prompt: "download", + confirm: (format: any) => { + if (req.value === null) return false; + layoutStore.closeHovers(); + + let files: string[] = []; + + for (let i of fileStore.selected) { + files.push(req.value.items[i].path); + } + + // @ts-ignore + api.download(format, hash.value, token.value, ...files); + }, + }); +}; + +const linkSelected = () => { + return isSingleFile() && req.value + ? // @ts-ignore + api.getDownloadURL({ + hash: hash.value, + path: req.value.items[fileStore.selected[0]].path, + }) + : ""; +}; + +onMounted(async () => { + // Created + hash.value = route.params.path[0]; + await fetchData(); + + window.addEventListener("keydown", keyEvent); + clip.value = new Clipboard(".copy-clipboard"); + clip.value.on("success", () => { + // $showSuccess(this.t("success.linkCopied")); + }); +}); + +onBeforeUnmount(() => { + window.removeEventListener("keydown", keyEvent); + clip.value.destroy(); +}); </script> diff --git a/frontend/src/views/files/FileListing.vue b/frontend/src/views/files/FileListing.vue index 8dd8c334..da520a14 100644 --- a/frontend/src/views/files/FileListing.vue +++ b/frontend/src/views/files/FileListing.vue @@ -616,7 +616,7 @@ export default { if (!items) return; let columns = Math.floor( - document.querySelector("main").offsetWidth / this.columnWidth + document.querySelector("main")?.offsetWidth / this.columnWidth ); if (columns === 0) columns = 1; items.style.width = `calc(${100 / columns}% - 1em)`; diff --git a/frontend/src/views/settings/Global.vue b/frontend/src/views/settings/Global.vue index 2dfca0d7..c94a6492 100644 --- a/frontend/src/views/settings/Global.vue +++ b/frontend/src/views/settings/Global.vue @@ -1,25 +1,25 @@ <template> <errors v-if="error" :errorCode="error.status" /> - <div class="row" v-else-if="!loading"> + <div class="row" v-else-if="!layoutStore.loading && settings !== null"> <div class="column"> <form class="card" @submit.prevent="save"> <div class="card-title"> - <h2>{{ $t("settings.globalSettings") }}</h2> + <h2>{{ t("settings.globalSettings") }}</h2> </div> <div class="card-content"> <p> <input type="checkbox" v-model="settings.signup" /> - {{ $t("settings.allowSignup") }} + {{ t("settings.allowSignup") }} </p> <p> <input type="checkbox" v-model="settings.createUserDir" /> - {{ $t("settings.createUserDir") }} + {{ t("settings.createUserDir") }} </p> <div> - <p class="small">{{ $t("settings.userHomeBasePath") }}</p> + <p class="small">{{ t("settings.userHomeBasePath") }}</p> <input class="input input--block" type="text" @@ -27,22 +27,22 @@ /> </div> - <h3>{{ $t("settings.rules") }}</h3> - <p class="small">{{ $t("settings.globalRules") }}</p> + <h3>{{ t("settings.rules") }}</h3> + <p class="small">{{ t("settings.globalRules") }}</p> <rules v-model:rules="settings.rules" /> - <div v-if="isExecEnabled"> - <h3>{{ $t("settings.executeOnShell") }}</h3> - <p class="small">{{ $t("settings.executeOnShellDescription") }}</p> + <div v-if="enableExec"> + <h3>{{ t("settings.executeOnShell") }}</h3> + <p class="small">{{ t("settings.executeOnShellDescription") }}</p> <input class="input input--block" type="text" placeholder="bash -c, cmd /c, ..." - v-model="settings.shell" + v-model="shellValue" /> </div> - <h3>{{ $t("settings.branding") }}</h3> + <h3>{{ t("settings.branding") }}</h3> <i18n-t keypath="settings.brandingHelp" @@ -54,7 +54,7 @@ class="link" target="_blank" href="https://filebrowser.org/configuration/custom-branding" - >{{ $t("settings.documentation") }}</a + >{{ t("settings.documentation") }}</a > </i18n-t> @@ -64,7 +64,7 @@ v-model="settings.branding.disableExternal" id="branding-links" /> - {{ $t("settings.disableExternalLinks") }} + {{ t("settings.disableExternalLinks") }} </p> <p> @@ -73,11 +73,11 @@ v-model="settings.branding.disableUsedPercentage" id="branding-links" /> - {{ $t("settings.disableUsedDiskPercentage") }} + {{ t("settings.disableUsedDiskPercentage") }} </p> <p> - <label for="theme">{{ $t("settings.themes.title") }}</label> + <label for="theme">{{ t("settings.themes.title") }}</label> <themes class="input input--block" v-model:theme="settings.branding.theme" @@ -86,7 +86,7 @@ </p> <p> - <label for="branding-name">{{ $t("settings.instanceName") }}</label> + <label for="branding-name">{{ t("settings.instanceName") }}</label> <input class="input input--block" type="text" @@ -97,7 +97,7 @@ <p> <label for="branding-files">{{ - $t("settings.brandingDirectoryPath") + t("settings.brandingDirectoryPath") }}</label> <input class="input input--block" @@ -107,14 +107,14 @@ /> </p> - <h3>{{ $t("settings.tusUploads") }}</h3> + <h3>{{ t("settings.tusUploads") }}</h3> - <p class="small">{{ $t("settings.tusUploadsHelp") }}</p> + <p class="small">{{ t("settings.tusUploadsHelp") }}</p> <div class="tusConditionalSettings"> <p> <label for="tus-chunkSize">{{ - $t("settings.tusUploadsChunkSize") + t("settings.tusUploadsChunkSize") }}</label> <input class="input input--block" @@ -126,7 +126,7 @@ <p> <label for="tus-retryCount">{{ - $t("settings.tusUploadsRetryCount") + t("settings.tusUploadsRetryCount") }}</label> <input class="input input--block" @@ -143,7 +143,7 @@ <input class="button button--flat" type="submit" - :value="$t('buttons.update')" + :value="t('buttons.update')" /> </div> </form> @@ -152,11 +152,11 @@ <div class="column"> <form class="card" @submit.prevent="save"> <div class="card-title"> - <h2>{{ $t("settings.userDefaults") }}</h2> + <h2>{{ t("settings.userDefaults") }}</h2> </div> <div class="card-content"> - <p class="small">{{ $t("settings.defaultUserDescription") }}</p> + <p class="small">{{ t("settings.defaultUserDescription") }}</p> <user-form :isNew="false" @@ -169,16 +169,16 @@ <input class="button button--flat" type="submit" - :value="$t('buttons.update')" + :value="t('buttons.update')" /> </div> </form> </div> <div class="column"> - <form v-if="isExecEnabled" class="card" @submit.prevent="save"> + <form v-if="enableExec" class="card" @submit.prevent="save"> <div class="card-title"> - <h2>{{ $t("settings.commandRunner") }}</h2> + <h2>{{ t("settings.commandRunner") }}</h2> </div> <div class="card-content"> @@ -194,24 +194,24 @@ class="link" target="_blank" href="https://filebrowser.org/configuration/command-runner" - >{{ $t("settings.documentation") }}</a + >{{ t("settings.documentation") }}</a > </i18n-t> <div - v-for="command in settings.commands" - :key="command.name" + v-for="(command, key) in settings.commands" + :key="key" class="collapsible" > - <input :id="command.name" type="checkbox" /> - <label :for="command.name"> - <p>{{ capitalize(command.name) }}</p> + <input :id="key" type="checkbox" /> + <label :for="key"> + <p>{{ capitalize(key) }}</p> <i class="material-icons">arrow_drop_down</i> </label> <div class="collapse"> <textarea class="input input--block input--textarea" - v-model.trim="command.value" + v-model.trim="commandObject[key]" ></textarea> </div> </div> @@ -221,7 +221,7 @@ <input class="button button--flat" type="submit" - :value="$t('buttons.update')" + :value="t('buttons.update')" /> </div> </form> @@ -229,9 +229,7 @@ </div> </template> -<script> -import { mapState, mapWritableState } from "pinia"; -import { useAuthStore } from "@/stores/auth"; +<script setup lang="ts"> import { useLayoutStore } from "@/stores/layout"; import { settings as api } from "@/api"; import { enableExec } from "@/utils/constants"; @@ -239,143 +237,162 @@ import UserForm from "@/components/settings/UserForm.vue"; import Rules from "@/components/settings/Rules.vue"; import Themes from "@/components/settings/Themes.vue"; import Errors from "@/views/Errors.vue"; +import { computed, inject, onBeforeUnmount, onMounted, ref } from "vue"; +import { useI18n } from "vue-i18n"; -export default { - name: "settings", - components: { - Themes, - UserForm, - Rules, - Errors, +const error = ref<any>(null); +const originalSettings = ref<ISettings | null>(null); +const settings = ref<ISettings | null>(null); +const debounceTimeout = ref<number | null>(null); + +const commandObject = ref<{ + [key in keyof SettingsCommand]: string; +}>({}); +const shellValue = ref<string>("") + +const $showError = inject<TToast>("$showError") as TToast; +const $showSuccess = inject<TToast>("$showSuccess") as TToast; + +const { t } = useI18n(); + +const layoutStore = useLayoutStore(); + +const formattedChunkSize = computed({ + get() { + return settings?.value?.tus?.chunkSize + ? formatBytes(settings?.value?.tus?.chunkSize) + : ""; }, - data: function () { - return { - error: null, - originalSettings: null, - settings: null, - debounceTimeout: null, - }; - }, - inject: ["$showError", "$showSuccess"], - computed: { - ...mapState(useAuthStore, ["user"]), - ...mapWritableState(useLayoutStore, ["loading"]), - isExecEnabled: () => enableExec, - formattedChunkSize: { - get() { - return this.formatBytes(this.settings.tus.chunkSize); - }, - set(value) { - // Use debouncing to allow the user to type freely without - // interruption by the formatter - // Clear the previous timeout if it exists - if (this.debounceTimeout) { - clearTimeout(this.debounceTimeout); - } - - // Set a new timeout to apply the format after a short delay - this.debounceTimeout = setTimeout(() => { - this.settings.tus.chunkSize = this.parseBytes(value); - }, 1500); - }, - }, - }, - async created() { - try { - this.loading = true; - - const original = await api.get(); - let settings = { ...original, commands: [] }; - - for (const key in original.commands) { - settings.commands.push({ - name: key, - value: original.commands[key].join("\n"), - }); - } - - settings.shell = settings.shell.join(" "); - - this.originalSettings = original; - this.settings = settings; - } catch (e) { - this.error = e; - } finally { - this.loading = false; + set(value: any) { + // Use debouncing to allow the user to type freely without + // interruption by the formatter + // Clear the previous timeout if it exists + if (debounceTimeout.value) { + clearTimeout(debounceTimeout.value); } + + // Set a new timeout to apply the format after a short delay + debounceTimeout.value = setTimeout(() => { + if (settings.value) settings.value.tus.chunkSize = parseBytes(value); + }, 1500); }, - methods: { - capitalize(name, where = "_") { - if (where === "caps") where = /(?=[A-Z])/; - let splitted = name.split(where); - name = ""; +}); - for (let i = 0; i < splitted.length; i++) { - name += - splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + " "; - } +// Define funcs +const capitalize = (name: string, where: string | RegExp = "_") => { + if (where === "caps") where = /(?=[A-Z])/; + let splitted = name.split(where); + name = ""; - return name.slice(0, -1); - }, - async save() { - let settings = { - ...this.settings, - shell: this.settings.shell - .trim() - .split(" ") - .filter((s) => s !== ""), - commands: {}, - }; + for (let i = 0; i < splitted.length; i++) { + name += splitted[i].charAt(0).toUpperCase() + splitted[i].slice(1) + " "; + } - for (const { name, value } of this.settings.commands) { - settings.commands[name] = value.split("\n").filter((cmd) => cmd !== ""); - } - - try { - await api.update(settings); - this.$showSuccess(this.$t("settings.settingsUpdated")); - } catch (e) { - this.$showError(e); - } - }, - // Parse the user-friendly input (e.g., "20M" or "1T") to bytes - parseBytes(input) { - const regex = /^(\d+)(\.\d+)?(B|K|KB|M|MB|G|GB|T|TB)?$/i; - const matches = input.match(regex); - if (matches) { - const size = parseFloat(matches[1].concat(matches[2] || "")); - let unit = matches[3].toUpperCase(); - if (!unit.endsWith("B")) { - unit += "B"; - } - const units = { - KB: 1024, - MB: 1024 ** 2, - GB: 1024 ** 3, - TB: 1024 ** 4, - }; - return size * (units[unit] || 1); - } else { - return 1024 ** 2; - } - }, - // Format the chunk size in bytes to user-friendly format - formatBytes(bytes) { - const units = ["B", "KB", "MB", "GB", "TB"]; - let size = bytes; - let unitIndex = 0; - while (size >= 1024 && unitIndex < units.length - 1) { - size /= 1024; - unitIndex++; - } - return `${size}${units[unitIndex]}`; - }, - // Clear the debounce timeout when the component is destroyed - beforeUnmount() { - if (this.debounceTimeout) { - clearTimeout(this.debounceTimeout); - } - }, - }, + return name.slice(0, -1); }; + +const save = async () => { + if (settings.value === null) return false; + let newSettings: ISettings = { + ...settings.value, + shell: + settings.value?.shell + .join(" ") + .trim() + .split(" ") + .filter((s: string) => s !== "") ?? [], + commands: {}, + }; + + // @ts-ignore + for (const name of Object.keys(settings.value.commands)) { + // @ts-ignore + const newValue = commandObject.value[name] + // @ts-ignore + if(name in commandObject.value && !Array.isArray(newValue)) { + // @ts-ignore + newSettings.commands[name] = newValue + .split("\n") + .filter((cmd: string) => cmd !== ""); + } else { + // @ts-ignore + newSettings.commands[name] = newValue + } + } + newSettings.shell = shellValue.value.split("\n"); + + try { + await api.update(newSettings); + $showSuccess(t("settings.settingsUpdated")); + } catch (e: any) { + $showError(e); + } +}; +// Parse the user-friendly input (e.g., "20M" or "1T") to bytes +const parseBytes = (input: string) => { + const regex = /^(\d+)(\.\d+)?(B|K|KB|M|MB|G|GB|T|TB)?$/i; + const matches = input.match(regex); + if (matches) { + const size = parseFloat(matches[1].concat(matches[2] || "")); + let unit: keyof SettingsUnit = + matches[3].toUpperCase() as keyof SettingsUnit; + if (!unit.endsWith("B")) { + unit += "B"; + } + const units: SettingsUnit = { + KB: 1024, + MB: 1024 ** 2, + GB: 1024 ** 3, + TB: 1024 ** 4, + }; + return size * (units[unit as keyof SettingsUnit] || 1); + } else { + return 1024 ** 2; + } +}; +// Format the chunk size in bytes to user-friendly format +const formatBytes = (bytes: number) => { + const units = ["B", "KB", "MB", "GB", "TB"]; + let size = bytes; + let unitIndex = 0; + while (size >= 1024 && unitIndex < units.length - 1) { + size /= 1024; + unitIndex++; + } + return `${size}${units[unitIndex]}`; +}; + +// Define Hooks + +onMounted(async () => { + try { + layoutStore.loading = true; + + const original: ISettings = await api.get(); + let newSettings: ISettings = { ...original, commands: {} }; + + for (const key in original.commands) { + // @ts-ignore + newSettings.commands[key] = original.commands[key]; + // @ts-ignore + commandObject.value[key] = original.commands[key].join("\n"); + } + + originalSettings.value = original; + settings.value = newSettings; + // @ts-ignore + shellValue.value = newSettings.shell.join("\n") + } catch (e) { + error.value = e; + } finally { + layoutStore.loading = false; + } +}); + +// Clear the debounce timeout when the component is destroyed +onBeforeUnmount(() => { + if (debounceTimeout.value) { + clearTimeout(debounceTimeout.value); + } +}); </script> diff --git a/frontend/src/vite-env.d.ts b/frontend/src/vite-env.d.ts new file mode 100644 index 00000000..11f02fe2 --- /dev/null +++ b/frontend/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/frontend/tsconfig.json b/frontend/tsconfig.json new file mode 100644 index 00000000..f182e33c --- /dev/null +++ b/frontend/tsconfig.json @@ -0,0 +1,30 @@ +{ + "compilerOptions": { + "baseUrl": ".", + "allowJs": true, + "target": "ESNext", + "useDefineForClassFields": true, + "module": "ESNext", + "moduleResolution": "Node", + "strict": true, + "jsx": "preserve", + "sourceMap": true, + "outDir": "../dist/frontend", + "resolveJsonModule": true, + "isolatedModules": true, + "esModuleInterop": true, + "lib": ["ESNext", "DOM"], + "skipLibCheck": true, + "types": ["vite/client"], + "typeRoots": ["./node_modules/@types", "./some-custom-lib"], + "paths": { + "@/*": ["./src/*"] + } + }, + "include": [ + "./**/*.ts", + "src/**/*.d.ts", + "src/**/*.vue", + ], + "exclude": ["node_modules", "dist"] +} diff --git a/frontend/vite.config.js b/frontend/vite.config.ts similarity index 98% rename from frontend/vite.config.js rename to frontend/vite.config.ts index 7413a5ab..2bb43020 100644 --- a/frontend/vite.config.js +++ b/frontend/vite.config.ts @@ -8,7 +8,7 @@ import pluginRewriteAll from "vite-plugin-rewrite-all"; const plugins = [ vue(), - VueI18nPlugin(), + VueI18nPlugin({}), legacy({ // defaults already drop IE support targets: ["defaults"], diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 00000000..4aabf2d8 --- /dev/null +++ b/package-lock.json @@ -0,0 +1,1632 @@ +{ + "name": "filebrowser", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "devDependencies": { + "@typescript-eslint/eslint-plugin": "^6.6.0" + } + }, + "node_modules/@aashutoshrathi/word-wrap": { + "version": "1.2.6", + "resolved": "https://registry.npmjs.org/@aashutoshrathi/word-wrap/-/word-wrap-1.2.6.tgz", + "integrity": "sha512-1Yjs2SvM8TflER/OD3cOjhWWOZb58A2t7wpE2S9XfBYTiIl+XFhQG2bjy4Pu1I+EAlCNUzRDYDdFwFYUKvXcIA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@eslint-community/eslint-utils": { + "version": "4.4.0", + "resolved": "https://registry.npmjs.org/@eslint-community/eslint-utils/-/eslint-utils-4.4.0.tgz", + "integrity": "sha512-1/sA4dwrzBAyeUoQ6oxahHKmrZvsnLCg4RfxW3ZFGGmQkSNQPFNLV9CUEFQP1x9EYXHTo5p6xdhZM1Ne9p/AfA==", + "dev": true, + "dependencies": { + "eslint-visitor-keys": "^3.3.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.0.0 || ^7.0.0 || >=8.0.0" + } + }, + "node_modules/@eslint-community/regexpp": { + "version": "4.8.0", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.8.0.tgz", + "integrity": "sha512-JylOEEzDiOryeUnFbQz+oViCXS0KsvR1mvHkoMiu5+UiBvy+RYX7tzlIIIEstF/gVa2tj9AQXk3dgnxv6KxhFg==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.2.tgz", + "integrity": "sha512-+wvgpDsrB1YqAMdEUCcnTlpfVBH7Vqn6A/NT3D8WVXFIaKMlErPIZT3oCIAVCOtarRpMtelZLqJeU3t7WY6X6g==", + "dev": true, + "peer": true, + "dependencies": { + "ajv": "^6.12.4", + "debug": "^4.3.2", + "espree": "^9.6.0", + "globals": "^13.19.0", + "ignore": "^5.2.0", + "import-fresh": "^3.2.1", + "js-yaml": "^4.1.0", + "minimatch": "^3.1.2", + "strip-json-comments": "^3.1.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/@eslint/js": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.49.0.tgz", + "integrity": "sha512-1S8uAY/MTJqVx0SC4epBq+N2yhuwtNwLbJYNZyhL2pO1ZVKn5HFXav5T41Ryzy9K9V7ZId2JB2oy/W4aCd9/2w==", + "dev": true, + "peer": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.11", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.11.tgz", + "integrity": "sha512-N2brEuAadi0CcdeMXUkhbZB84eskAc8MEX1By6qEchoVywSgXPIjou4rYsl0V3Hj0ZnuGycGCjdNgockbzeWNA==", + "dev": true, + "peer": true, + "dependencies": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + }, + "engines": { + "node": ">=10.10.0" + } + }, + "node_modules/@humanwhocodes/module-importer": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz", + "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=12.22" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/nzakas" + } + }, + "node_modules/@humanwhocodes/object-schema": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz", + "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==", + "dev": true, + "peer": true + }, + "node_modules/@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "dependencies": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/@types/json-schema": { + "version": "7.0.12", + "resolved": "https://registry.npmjs.org/@types/json-schema/-/json-schema-7.0.12.tgz", + "integrity": "sha512-Hr5Jfhc9eYOQNPYO5WLDq/n4jqijdHNlDXjuAQkkt+mWdQR+XJToOHrsD4cPaMXpn6KO7y2+wM8AZEs8VpBLVA==", + "dev": true + }, + "node_modules/@types/semver": { + "version": "7.5.1", + "resolved": "https://registry.npmjs.org/@types/semver/-/semver-7.5.1.tgz", + "integrity": "sha512-cJRQXpObxfNKkFAZbJl2yjWtJCqELQIdShsogr1d2MilP8dKD9TE/nEKHkJgUNHdGKCQaf9HbIynuV2csLGVLg==", + "dev": true + }, + "node_modules/@typescript-eslint/eslint-plugin": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-6.6.0.tgz", + "integrity": "sha512-CW9YDGTQnNYMIo5lMeuiIG08p4E0cXrXTbcZ2saT/ETE7dWUrNxlijsQeU04qAAKkILiLzdQz+cGFxCJjaZUmA==", + "dev": true, + "dependencies": { + "@eslint-community/regexpp": "^4.5.1", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/type-utils": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4", + "graphemer": "^1.4.0", + "ignore": "^5.2.4", + "natural-compare": "^1.4.0", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "@typescript-eslint/parser": "^6.0.0 || ^6.0.0-alpha", + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/parser": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-6.6.0.tgz", + "integrity": "sha512-setq5aJgUwtzGrhW177/i+DMLqBaJbdwGj2CPIVFFLE0NCliy5ujIdLHd2D1ysmlmsjdL2GWW+hR85neEfc12w==", + "dev": true, + "peer": true, + "dependencies": { + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/scope-manager": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-6.6.0.tgz", + "integrity": "sha512-pT08u5W/GT4KjPUmEtc2kSYvrH8x89cVzkA0Sy2aaOUIw6YxOIjA8ilwLr/1fLjOedX1QAuBpG9XggWqIIfERw==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/type-utils": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/type-utils/-/type-utils-6.6.0.tgz", + "integrity": "sha512-8m16fwAcEnQc69IpeDyokNO+D5spo0w1jepWWY2Q6y5ZKNuj5EhVQXjtVAeDDqvW6Yg7dhclbsz6rTtOvcwpHg==", + "dev": true, + "dependencies": { + "@typescript-eslint/typescript-estree": "6.6.0", + "@typescript-eslint/utils": "6.6.0", + "debug": "^4.3.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/types": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-6.6.0.tgz", + "integrity": "sha512-CB6QpJQ6BAHlJXdwUmiaXDBmTqIE2bzGTDLADgvqtHWuhfNP3rAOK7kAgRMAET5rDRr9Utt+qAzRBdu3AhR3sg==", + "dev": true, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/@typescript-eslint/typescript-estree": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-6.6.0.tgz", + "integrity": "sha512-hMcTQ6Al8MP2E6JKBAaSxSVw5bDhdmbCEhGW/V8QXkb9oNsFkA4SBuOMYVPxD3jbtQ4R/vSODBsr76R6fP3tbA==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/visitor-keys": "6.6.0", + "debug": "^4.3.4", + "globby": "^11.1.0", + "is-glob": "^4.0.3", + "semver": "^7.5.4", + "ts-api-utils": "^1.0.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@typescript-eslint/utils": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/utils/-/utils-6.6.0.tgz", + "integrity": "sha512-mPHFoNa2bPIWWglWYdR0QfY9GN0CfvvXX1Sv6DlSTive3jlMTUy+an67//Gysc+0Me9pjitrq0LJp0nGtLgftw==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.4.0", + "@types/json-schema": "^7.0.12", + "@types/semver": "^7.5.0", + "@typescript-eslint/scope-manager": "6.6.0", + "@typescript-eslint/types": "6.6.0", + "@typescript-eslint/typescript-estree": "6.6.0", + "semver": "^7.5.4" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + }, + "peerDependencies": { + "eslint": "^7.0.0 || ^8.0.0" + } + }, + "node_modules/@typescript-eslint/visitor-keys": { + "version": "6.6.0", + "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-6.6.0.tgz", + "integrity": "sha512-L61uJT26cMOfFQ+lMZKoJNbAEckLe539VhTxiGHrWl5XSKQgA0RTBZJW2HFPy5T0ZvPVSD93QsrTKDkfNwJGyQ==", + "dev": true, + "dependencies": { + "@typescript-eslint/types": "6.6.0", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^16.0.0 || >=18.0.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/typescript-eslint" + } + }, + "node_modules/acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true, + "peer": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/acorn-jsx": { + "version": "5.3.2", + "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz", + "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==", + "dev": true, + "peer": true, + "peerDependencies": { + "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "peer": true, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dev": true, + "peer": true, + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/argparse": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz", + "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q==", + "dev": true, + "peer": true + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/balanced-match": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz", + "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==", + "dev": true, + "peer": true + }, + "node_modules/brace-expansion": { + "version": "1.1.11", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz", + "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==", + "dev": true, + "peer": true, + "dependencies": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "node_modules/braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "dependencies": { + "fill-range": "^7.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dev": true, + "peer": true, + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "dev": true, + "peer": true + }, + "node_modules/concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true, + "peer": true + }, + "node_modules/cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "peer": true, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "dependencies": { + "ms": "2.1.2" + }, + "engines": { + "node": ">=6.0" + }, + "peerDependenciesMeta": { + "supports-color": { + "optional": true + } + } + }, + "node_modules/deep-is": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz", + "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ==", + "dev": true, + "peer": true + }, + "node_modules/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, + "dependencies": { + "path-type": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "peer": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.49.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.49.0.tgz", + "integrity": "sha512-jw03ENfm6VJI0jA9U+8H5zfl5b+FvuU3YYvZRdZHOlU2ggJkxrlkJH4HcDrZpj6YwD8kuYqvQM8LyesoazrSOQ==", + "dev": true, + "peer": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.6.1", + "@eslint/eslintrc": "^2.1.2", + "@eslint/js": "8.49.0", + "@humanwhocodes/config-array": "^0.11.11", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.12.4", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.2", + "eslint-visitor-keys": "^3.4.3", + "espree": "^9.6.1", + "esquery": "^1.4.2", + "esutils": "^2.0.2", + "fast-deep-equal": "^3.1.3", + "file-entry-cache": "^6.0.1", + "find-up": "^5.0.0", + "glob-parent": "^6.0.2", + "globals": "^13.19.0", + "graphemer": "^1.4.0", + "ignore": "^5.2.0", + "imurmurhash": "^0.1.4", + "is-glob": "^4.0.0", + "is-path-inside": "^3.0.3", + "js-yaml": "^4.1.0", + "json-stable-stringify-without-jsonify": "^1.0.1", + "levn": "^0.4.1", + "lodash.merge": "^4.6.2", + "minimatch": "^3.1.2", + "natural-compare": "^1.4.0", + "optionator": "^0.9.3", + "strip-ansi": "^6.0.1", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.2", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.2.tgz", + "integrity": "sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==", + "dev": true, + "peer": true, + "dependencies": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-visitor-keys": { + "version": "3.4.3", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.3.tgz", + "integrity": "sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/espree": { + "version": "9.6.1", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.1.tgz", + "integrity": "sha512-oruZaFkjorTpF32kDSI5/75ViwGeZginGGy2NoOSg3Q9bnwlnmDm4HLnkl0RE3n+njDXR037aY1+x58Z/zFdwQ==", + "dev": true, + "peer": true, + "dependencies": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.1.0" + }, + "engines": { + "node": ">=0.10" + } + }, + "node_modules/esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "peer": true, + "dependencies": { + "estraverse": "^5.2.0" + }, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true, + "peer": true + }, + "node_modules/fast-glob": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.1.tgz", + "integrity": "sha512-kNFPyjhh5cKjrUltxs+wFx+ZkbRaxxmZ+X0ZU31SOsxCEtP9VPgtq2teZw1DebupL5GmDaNQ6yKMMVcM41iqDg==", + "dev": true, + "dependencies": { + "@nodelib/fs.stat": "^2.0.2", + "@nodelib/fs.walk": "^1.2.3", + "glob-parent": "^5.1.2", + "merge2": "^1.3.0", + "micromatch": "^4.0.4" + }, + "engines": { + "node": ">=8.6.0" + } + }, + "node_modules/fast-glob/node_modules/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, + "dependencies": { + "is-glob": "^4.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true, + "peer": true + }, + "node_modules/fast-levenshtein": { + "version": "2.0.6", + "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz", + "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw==", + "dev": true, + "peer": true + }, + "node_modules/fastq": { + "version": "1.15.0", + "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.15.0.tgz", + "integrity": "sha512-wBrocU2LCXXa+lWBt8RoIRD89Fi8OdABODa/kEnyeyjS5aZO5/GNvI5sEINADqP/h8M29UHTHUb53sUu5Ihqdw==", + "dev": true, + "dependencies": { + "reusify": "^1.0.4" + } + }, + "node_modules/file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "peer": true, + "dependencies": { + "flat-cache": "^3.0.4" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/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, + "dependencies": { + "to-regex-range": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/find-up": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz", + "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==", + "dev": true, + "peer": true, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.1.0.tgz", + "integrity": "sha512-OHx4Qwrrt0E4jEIcI5/Xb+f+QmJYNj2rrK8wiIdQOIrB9WrrJL8cjZvXdXuBTkkEwEqLycb5BeZDV1o2i9bTew==", + "dev": true, + "peer": true, + "dependencies": { + "flatted": "^3.2.7", + "keyv": "^4.5.3", + "rimraf": "^3.0.2" + }, + "engines": { + "node": ">=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true, + "peer": true + }, + "node_modules/fs.realpath": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz", + "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==", + "dev": true, + "peer": true + }, + "node_modules/glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "peer": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "peer": true, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.21.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.21.0.tgz", + "integrity": "sha512-ybyme3s4yy/t/3s35bewwXKOf7cvzfreG2lH0lZl0JB7I4GxRP2ghxOK/Nb9EkRXdbBXZLfq/p/0W2JUONB/Gg==", + "dev": true, + "peer": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/globby": { + "version": "11.1.0", + "resolved": "https://registry.npmjs.org/globby/-/globby-11.1.0.tgz", + "integrity": "sha512-jhIXaOzy1sb8IyocaruWSn1TjmnBVs8Ayhcy83rmxNJ8q2uWKCAj3CnJY+KpGSXCueAPc0i05kVvVKtP1t9S3g==", + "dev": true, + "dependencies": { + "array-union": "^2.1.0", + "dir-glob": "^3.0.1", + "fast-glob": "^3.2.9", + "ignore": "^5.2.0", + "merge2": "^1.4.1", + "slash": "^3.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true, + "engines": { + "node": ">= 4" + } + }, + "node_modules/import-fresh": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz", + "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==", + "dev": true, + "peer": true, + "dependencies": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.8.19" + } + }, + "node_modules/inflight": { + "version": "1.0.6", + "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz", + "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==", + "dev": true, + "peer": true, + "dependencies": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true, + "peer": true + }, + "node_modules/is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/is-glob": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz", + "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==", + "dev": true, + "dependencies": { + "is-extglob": "^2.1.1" + }, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/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, + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/is-path-inside": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz", + "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true, + "peer": true + }, + "node_modules/js-yaml": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz", + "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==", + "dev": true, + "peer": true, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-buffer": { + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/json-buffer/-/json-buffer-3.0.1.tgz", + "integrity": "sha512-4bV5BfR2mqfQTJm+V5tPPdf+ZpuhiIvTuAB5g8kcrXOZpTT/QwwVRWBywX1ozr6lEuPdbHxwaJlm9G6mI2sfSQ==", + "dev": true, + "peer": true + }, + "node_modules/json-schema-traverse": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz", + "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg==", + "dev": true, + "peer": true + }, + "node_modules/json-stable-stringify-without-jsonify": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz", + "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw==", + "dev": true, + "peer": true + }, + "node_modules/keyv": { + "version": "4.5.3", + "resolved": "https://registry.npmjs.org/keyv/-/keyv-4.5.3.tgz", + "integrity": "sha512-QCiSav9WaX1PgETJ+SpNnx2PRRapJ/oRSXM4VO5OGYGSjrxbKPVFVhB3l2OCbLCk329N8qyAtsJjSjvVBWzEug==", + "dev": true, + "peer": true, + "dependencies": { + "json-buffer": "3.0.1" + } + }, + "node_modules/levn": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz", + "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/locate-path": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz", + "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==", + "dev": true, + "peer": true, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash.merge": { + "version": "4.6.2", + "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz", + "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ==", + "dev": true, + "peer": true + }, + "node_modules/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, + "dependencies": { + "yallist": "^4.0.0" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true, + "engines": { + "node": ">= 8" + } + }, + "node_modules/micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "dependencies": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + }, + "engines": { + "node": ">=8.6" + } + }, + "node_modules/minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "peer": true, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/ms": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz", + "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==", + "dev": true + }, + "node_modules/natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "node_modules/once": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz", + "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==", + "dev": true, + "peer": true, + "dependencies": { + "wrappy": "1" + } + }, + "node_modules/optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "peer": true, + "dependencies": { + "@aashutoshrathi/word-wrap": "^1.2.3", + "deep-is": "^0.1.3", + "fast-levenshtein": "^2.0.6", + "levn": "^0.4.1", + "prelude-ls": "^1.2.1", + "type-check": "^0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "peer": true, + "dependencies": { + "yocto-queue": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-locate": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz", + "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==", + "dev": true, + "peer": true, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/parent-module": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz", + "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==", + "dev": true, + "peer": true, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true, + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/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, + "engines": { + "node": ">=8" + } + }, + "node_modules/picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true, + "engines": { + "node": ">=8.6" + }, + "funding": { + "url": "https://github.com/sponsors/jonschlinkert" + } + }, + "node_modules/prelude-ls": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz", + "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==", + "dev": true, + "peer": true, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true, + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/queue-microtask": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz", + "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ] + }, + "node_modules/resolve-from": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz", + "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==", + "dev": true, + "peer": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true, + "engines": { + "iojs": ">=1.0.0", + "node": ">=0.10.0" + } + }, + "node_modules/rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "peer": true, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "dependencies": { + "queue-microtask": "^1.2.2" + } + }, + "node_modules/semver": { + "version": "7.5.4", + "resolved": "https://registry.npmjs.org/semver/-/semver-7.5.4.tgz", + "integrity": "sha512-1bCSESV6Pv+i21Hvpxp3Dx+pSD8lIPt8uVjRrxAUt/nbswYc+tK6Y2btiULjd4+fnq15PX+nqQDC7Oft7WkwcA==", + "dev": true, + "dependencies": { + "lru-cache": "^6.0.0" + }, + "bin": { + "semver": "bin/semver.js" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "peer": true, + "dependencies": { + "shebang-regex": "^3.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/slash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/slash/-/slash-3.0.0.tgz", + "integrity": "sha512-g9Q1haeby36OSStwb4ntCGGGaKsaVSjQ68fBxoQcutl5fS1vuY18H3wSt3jFyFtrkx+Kz0V1G85A4MyAdDMi2Q==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dev": true, + "peer": true, + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "peer": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "dev": true, + "peer": true, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true, + "peer": true + }, + "node_modules/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, + "dependencies": { + "is-number": "^7.0.0" + }, + "engines": { + "node": ">=8.0" + } + }, + "node_modules/ts-api-utils": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/ts-api-utils/-/ts-api-utils-1.0.3.tgz", + "integrity": "sha512-wNMeqtMz5NtwpT/UZGY5alT+VoKdSsOOP/kqHFcUW1P/VRhH2wJ48+DN2WwUliNbQ976ETwDL0Ifd2VVvgonvg==", + "dev": true, + "engines": { + "node": ">=16.13.0" + }, + "peerDependencies": { + "typescript": ">=4.2.0" + } + }, + "node_modules/type-check": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz", + "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==", + "dev": true, + "peer": true, + "dependencies": { + "prelude-ls": "^1.2.1" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.2.2", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.2.2.tgz", + "integrity": "sha512-mI4WrpHsbCIcwT9cF4FZvr80QUeKvsUsUvKDoR+X/7XHQH98xYD8YHZg7ANtz2GtZt/CBq2QJ0thkGJMHfqc1w==", + "dev": true, + "peer": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "peer": true, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "peer": true, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/wrappy": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz", + "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==", + "dev": true, + "peer": true + }, + "node_modules/yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "node_modules/yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true, + "peer": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json new file mode 100644 index 00000000..9e5610b7 --- /dev/null +++ b/package.json @@ -0,0 +1,4 @@ +{ + "devDependencies": { + } +}