diff options
Diffstat (limited to 'front-end')
81 files changed, 16419 insertions, 0 deletions
diff --git a/front-end/.env b/front-end/.env new file mode 100644 index 0000000..1b4c9eb --- /dev/null +++ b/front-end/.env @@ -0,0 +1,18 @@ +#The base url for all api requests +VITE_API_URL="/api" +VITE_CORS_ENABLED=false + +#The VNLib.Plugins.Essentials.Accounts plugin security header value +VITE_WEB_TOKEN_HEADER="X-Web-Token" +VITE_LOGIN_COOKIE_ID="li" + +#The path to the accounts plugin api +VITE_ACCOUNTS_BASE_PATH="/account" + +#If true enables the PKI login method +VITE_PKI_ENDPOINT="/account/pki" +#If true enables the Auth0 login method +VITE_ENABLE_AUTH0=false + +#The path to the blog admin api +VITE_BLOG_ADMIN_URL="/blog"
\ No newline at end of file diff --git a/front-end/.gitignore b/front-end/.gitignore new file mode 100644 index 0000000..104b797 --- /dev/null +++ b/front-end/.gitignore @@ -0,0 +1,24 @@ +# Logs +logs +*.log +npm-debug.log* +yarn-debug.log* +yarn-error.log* +pnpm-debug.log* +lerna-debug.log* + +node_modules +dist +dist-ssr +*.local* + +# Editor directories and files +.vscode/* +!.vscode/extensions.json +.idea +.DS_Store +*.suo +*.ntvs* +*.njsproj +*.sln +*.sw?
\ No newline at end of file diff --git a/front-end/.vscode/extensions.json b/front-end/.vscode/extensions.json new file mode 100644 index 0000000..c0a6e5a --- /dev/null +++ b/front-end/.vscode/extensions.json @@ -0,0 +1,3 @@ +{ + "recommendations": ["Vue.volar", "Vue.vscode-typescript-vue-plugin"] +} diff --git a/front-end/README.md b/front-end/README.md new file mode 100644 index 0000000..9329c2d --- /dev/null +++ b/front-end/README.md @@ -0,0 +1,49 @@ +# CMNext/front-end + +Forked from [@vnuge/vn-acc-starter-project](https://github.com/VnUgE/vn-acc-starter-project) + +A single page application front-end for managing content and publishing blog posts to your S3 storage across infinite channels. When served, gives you account access to administrate your blog publishing system. + +## Getting Started + +Basic repo setup and git information can be found in the [README.md](../README.md) + +Setting up the front-end. This directory contains all the project code to build the admin portal for your blog. It is a vuejs, vite, tailwindcss project. It requires a few backend VNLib plugins which you can refer to the [README.md](../README.md) in the root directory for more information. A build produces a single page app and is output in the dist directory on a build. + +### Project setup, install NPM dependencies + +```shell +npm install +``` + +### Compiles and hot-reloads for development + +```shell +npm run dev +``` + +### Build for production + +```shell +npm run build +``` + +Finally, copy the output (dist) directory into the root of your webserver and configure routing according to the vue-router. + +## Static front end hosting + +If you choose to host this front-end outside of the VNLib.Webserver backend, follow the instructions from the [vue-router documentation](https://router.vuejs.org/guide/essentials/history-mode.html#example-server-configurations) + +If you choose to host the static site outside of the server hosting the API, you will need to adjust the 'VITE*API*URL' in the .env file to point to the API server. This variable tells the front-end where to send API requests. You will need to configure your proxy server to route these requests to the API server at whatever routes you configured in your backend. + +The backend plugin has a configuration file that allows you to adjust these api paths/routes. These endpoints must match the 'VITE*API*URL' set in your .env file. + +## Third party library info + +This project injects a remote CDN script from [CKEditor](https://ckeditor.com/) (their superbuild package) to provide a robust rich text editing experience. It is quite a large package, but it is one of the best editors I have come across. + +Markdown support is included with [Showdownjs](https://github.com/showdownjs/showdown) and bundled directly in the package because it is rather small compared. + +The comprehensive JSON editor for feed fields is provided by [Vue-Json-Editor](https://github.com/cloydlau/json-editor-vue) + +There are a number of other foss packages that make this project possible, so please check out the package file and consider supporting them. diff --git a/front-end/index.html b/front-end/index.html new file mode 100644 index 0000000..966d9b5 --- /dev/null +++ b/front-end/index.html @@ -0,0 +1,16 @@ +<!DOCTYPE html> +<html lang="en"> + <head> + <meta charset="UTF-8" /> + <meta http-equiv="X-UA-Compatible" content="IE=edge"> + <meta name="viewport" content="width=device-width, initial-scale=1.0" /> + <link rel="icon" type="image/svg+xml" href="data:image/svg+xml,<svg xmlns=%22http://www.w3.org/2000/svg%22 width=%22256%22 height=%22256%22 viewBox=%220 0 100 100%22><rect width=%22100%22 height=%22100%22 rx=%2220%22 fill=%22%2352d9a7%22></rect><path d=%22M33.20 71.71L33.20 71.71Q32.45 72.31 30.80 72.84Q29.15 73.36 27.20 73.36L27.20 73.36Q24.87 73.36 23.15 72.69Q21.42 72.01 20.82 70.74L20.82 70.74Q19.92 68.94 18.69 66.01Q17.45 63.09 16.06 59.53Q14.67 55.96 13.25 51.95Q11.82 47.94 10.44 43.93Q9.05 39.91 7.85 36.13Q6.65 32.34 5.82 29.11L5.82 29.11Q6.57 28.29 8.04 27.61Q9.50 26.94 11.22 26.94L11.22 26.94Q13.32 26.94 14.49 27.88Q15.65 28.81 16.40 31.06L16.40 31.06Q19.02 39.46 21.61 47.41Q24.20 55.36 27.12 63.69L27.12 63.69L27.50 63.69Q28.85 59.86 30.24 55.51Q31.62 51.16 32.97 46.55Q34.32 41.94 35.64 37.25Q36.95 32.56 38.07 27.99L38.07 27.99Q40.02 26.94 42.65 26.94L42.65 26.94Q44.75 26.94 46.14 27.88Q47.52 28.81 47.52 30.99L47.52 30.99Q47.52 32.34 46.70 35.49Q45.88 38.64 44.60 42.65Q43.32 46.66 41.71 51.16Q40.10 55.66 38.49 59.75Q36.88 63.84 35.49 67.03Q34.10 70.21 33.20 71.71ZM60.50 73.29L60.50 73.29Q57.72 73.29 56.60 72.28Q55.47 71.26 55.47 68.86L55.47 68.86L55.47 28.89Q56.30 27.84 57.91 27.24Q59.52 26.64 61.47 26.64L61.47 26.64Q63.80 26.64 65.64 27.46Q67.47 28.29 68.90 30.76L68.90 30.76L79.70 50.19Q81.20 52.89 82.59 55.78Q83.97 58.66 85.02 61.06L85.02 61.06L85.32 60.99Q84.95 56.56 84.84 52.40Q84.72 48.24 84.72 44.19L84.72 44.19L84.72 27.16Q85.32 27.01 86.56 26.83Q87.80 26.64 89.07 26.64L89.07 26.64Q91.85 26.64 93.01 27.65Q94.17 28.66 94.17 31.06L94.17 31.06L94.17 71.56Q93.35 72.39 91.81 72.84Q90.27 73.29 88.17 73.29L88.17 73.29Q85.92 73.29 84.09 72.50Q82.25 71.71 80.82 69.24L80.82 69.24L70.02 49.81Q68.52 47.19 67.14 44.23Q65.75 41.26 64.70 38.86L64.70 38.86L64.25 38.94Q64.63 43.06 64.74 47.41Q64.85 51.76 64.85 55.66L64.85 55.66L64.85 72.69Q64.25 72.84 63.05 73.06Q61.85 73.29 60.50 73.29Z%22 fill=%22%23000000%22></path></svg>" /> + </head> + <body> + <noscript> + <strong>We're sorry but this site doesn't work properly without JavaScript enabled. Please enable it to continue.</strong> + </noscript> + <div id="app"></div> + <script type="module" src="/src/main.ts"></script> + </body> +</html> diff --git a/front-end/package-lock.json b/front-end/package-lock.json new file mode 100644 index 0000000..3e7c17c --- /dev/null +++ b/front-end/package-lock.json @@ -0,0 +1,8902 @@ +{ + "name": "@vnuge/cmnext-front-end", + "version": "0.1.0", + "lockfileVersion": 2, + "requires": true, + "packages": { + "": { + "name": "@vnuge/cmnext-front-end", + "version": "0.1.0", + "dependencies": { + "@chenfengyuan/vue-qrcode": "^2.0.0", + "@fontsource/nunito": "^5.0.3", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/vue-fontawesome": "^3.0.3", + "@headlessui/vue": "^1.7.12", + "@kyvg/vue3-notification": "^2.9.0", + "@vnuge/cmnext-admin": "../lib/admin", + "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/releases/vnlib-browser/v0.1.5.tgz", + "@vuelidate/core": "^2.0.2", + "@vuelidate/validators": "^2.0.2", + "@vueuse/core": "^10.1.2", + "@vueuse/router": "^10.1.2", + "axios": "^1.4.0", + "base32-encode": "^2.0.0", + "jose": "^4.14.4", + "json-editor-vue": "^0.10.6", + "lodash": "^4.17.21", + "otpauth": "^9.1.2", + "showdown": "^2.1.0", + "universal-cookie": "^4.0.4", + "vanilla-jsoneditor": "^0.17.8", + "vue": "^3.2.47", + "vue3-otp-input": "^0.4.1" + }, + "devDependencies": { + "@types/lodash": "^4.14.194", + "@types/showdown": "^2.0.1", + "@vitejs/plugin-vue": "^4.1.0", + "@volar-plugins/vetur": "latest", + "autoprefixer": "^10.4.14", + "dotenv": "^16.0.3", + "postcss": "^8.4.23", + "sass": "^1.62.1", + "tailwindcss": "^3.3.2", + "typescript": "^5.0.2", + "vite": "^4.3.5", + "vite-plugin-pages": "^0.31.0", + "vue-eslint-parser": "^9.3.0", + "vue-router": "^4.2.0", + "vue-tsc": "^1.4.2" + } + }, + "../lib/admin": { + "name": "@vnuge/cmnext-admin", + "version": "0.1.0", + "license": "agpl3", + "devDependencies": { + "@babel/types": "^7.x", + "@types/lodash": "^4.14.194", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "axios": "^1.x", + "eslint": "^8.39.0", + "jose": "^4.13.x", + "universal-cookie": "^4.0.4" + }, + "peerDependencies": { + "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/releases/vnlib-browser/v0.1.5.tgz", + "@vueuse/core": "^10.x", + "@vueuse/router": "^10.x", + "lodash": "^4.x", + "vue": "^3.x" + } + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "dependencies": { + "@babel/highlight": "^7.22.5" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "dependencies": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "engines": { + "node": ">=6.9.0" + } + }, + "node_modules/@babel/highlight/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/@babel/highlight/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/@babel/highlight/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/@babel/highlight/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/highlight/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==", + "bin": { + "parser": "bin/babel-parser.js" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@chenfengyuan/vue-qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chenfengyuan/vue-qrcode/-/vue-qrcode-2.0.0.tgz", + "integrity": "sha512-33Cfr0zjbc3Dd8d5b1IgzXRAgXH0c2Gv19VI4snS25V/x9Z41eg769tC+Us1x+vqgQQhgD5YUjLnkpkrQfeMSw==", + "peerDependencies": { + "qrcode": "^1.5.0", + "vue": "^3.0.0" + } + }, + "node_modules/@esbuild/android-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz", + "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz", + "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/android-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz", + "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "android" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz", + "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/darwin-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz", + "integrity": "sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz", + "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/freebsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz", + "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "freebsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz", + "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==", + "cpu": [ + "arm" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz", + "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz", + "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-loong64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz", + "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==", + "cpu": [ + "loong64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-mips64el": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz", + "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==", + "cpu": [ + "mips64el" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-ppc64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz", + "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==", + "cpu": [ + "ppc64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-riscv64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz", + "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==", + "cpu": [ + "riscv64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-s390x": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz", + "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==", + "cpu": [ + "s390x" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/linux-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz", + "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "linux" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/netbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz", + "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "netbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/openbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz", + "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "openbsd" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/sunos-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz", + "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "sunos" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz", + "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==", + "cpu": [ + "arm64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz", + "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==", + "cpu": [ + "ia32" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "node_modules/@esbuild/win32-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz", + "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==", + "cpu": [ + "x64" + ], + "dev": true, + "optional": true, + "os": [ + "win32" + ], + "engines": { + "node": ">=12" + } + }, + "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.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true, + "engines": { + "node": "^12.0.0 || ^14.0.0 || >=16.0.0" + } + }, + "node_modules/@eslint/eslintrc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "dev": 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.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "dev": true, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + } + }, + "node_modules/@fontsource/nunito": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@fontsource/nunito/-/nunito-5.0.5.tgz", + "integrity": "sha512-y98oMry6UQM1L0kffWeRTTWu9PVBoFcX44l3vkHynSfk0BRHxxCDnm+uDPYYiIud7Qnwd3cZubw0Z4JwazXOWw==" + }, + "node_modules/@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==", + "hasInstallScript": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "hasInstallScript": true, + "dependencies": { + "@fortawesome/fontawesome-common-types": "6.4.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/@fortawesome/vue-fontawesome": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.3.tgz", + "integrity": "sha512-KCPHi9QemVXGMrfuwf3nNnNo129resAIQWut9QTAMXmXqL2ErABC6ohd2yY5Ipq0CLWNbKHk8TMdTXL/Zf3ZhA==", + "peerDependencies": { + "@fortawesome/fontawesome-svg-core": "~1 || ~6", + "vue": ">= 3.0.0 < 4" + } + }, + "node_modules/@headlessui/vue": { + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.14.tgz", + "integrity": "sha512-aL9U9Sa7wdOzlrfjx6EjMIYNRCma5mngWcWzQBcHFwznpRZ8g/QZ/AYFtRDrZZUw22Ttttja4D7ZRXFwhONewA==", + "engines": { + "node": ">=10" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": 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, + "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 + }, + "node_modules/@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "dependencies": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "node_modules/@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "dependencies": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + } + }, + "node_modules/@jridgewell/trace-mapping/node_modules/@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + }, + "node_modules/@kyvg/vue3-notification": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@kyvg/vue3-notification/-/vue3-notification-2.9.1.tgz", + "integrity": "sha512-FsY8g25tQetr3etnarxHtCeNFKssH8sheFu13LyL2JJmOOel437QqKV5n4RBDDDTIo55iKgIVYXeojliXYdEhw==", + "peerDependencies": { + "vue": "^3.0.0" + } + }, + "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/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "node_modules/@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dev": true, + "dependencies": { + "@types/ms": "*" + } + }, + "node_modules/@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "node_modules/@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "node_modules/@types/showdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.1.tgz", + "integrity": "sha512-xdnAw2nFqomkaL0QdtEk0t7yz26UkaVPl4v1pYJvtE1T0fmfQEH3JaxErEhGByEAl3zUZrkNBlneuJp0WJGqEA==", + "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/@vitejs/plugin-vue": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", + "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "dev": true, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "peerDependencies": { + "vite": "^4.0.0", + "vue": "^3.2.25" + } + }, + "node_modules/@vnuge/cmnext-admin": { + "resolved": "../lib/admin", + "link": true + }, + "node_modules/@vnuge/vnlib.browser": { + "version": "0.1.5", + "resolved": "https://www.vaughnnugent.com/public/resources/software/releases/vnlib-browser/v0.1.5.tgz", + "integrity": "sha512-O1iJ9F0e4y3dvGGQy/qI4FbeMAU7UUcJpA/4totci9GoslBL6ePRNS5NRpcLI3LPVH3fLbZTiOeH/L2F6ajV+g==", + "license": "ISC", + "peerDependencies": { + "@vueuse/core": "^10.x", + "lodash": "^4.x", + "vue": "^3.x", + "vue-router": "^4.x" + } + }, + "node_modules/@volar-plugins/vetur": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@volar-plugins/vetur/-/vetur-2.0.0.tgz", + "integrity": "sha512-RsubwRwnv497I8Dtru8gE/C6P+nmvmMLNnQnyLU3njTqO5Dm4KaBr6cbVl8iQOXHOxfBXzI3lmQSPWdsp3dXRA==", + "dev": true, + "dependencies": { + "vls": "^0.8.2", + "vscode-html-languageservice": "^5.0.4" + }, + "peerDependencies": { + "@volar/language-service": "*" + }, + "peerDependenciesMeta": { + "@volar/language-service": { + "optional": true + } + } + }, + "node_modules/@volar/language-core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.8.0.tgz", + "integrity": "sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==", + "dev": true, + "dependencies": { + "@volar/source-map": "1.8.0" + } + }, + "node_modules/@volar/source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.8.0.tgz", + "integrity": "sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==", + "dev": true, + "dependencies": { + "muggle-string": "^0.3.1" + } + }, + "node_modules/@volar/typescript": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.8.0.tgz", + "integrity": "sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==", + "dev": true, + "dependencies": { + "@volar/language-core": "1.8.0" + } + }, + "node_modules/@vscode/l10n": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.14.tgz", + "integrity": "sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg==", + "dev": true + }, + "node_modules/@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "dependencies": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "dependencies": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "node_modules/@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "node_modules/@vue/language-core": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.4.tgz", + "integrity": "sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==", + "dev": true, + "dependencies": { + "@volar/language-core": "~1.8.0", + "@volar/source-map": "~1.8.0", + "@vue/compiler-dom": "^3.3.0", + "@vue/reactivity": "^3.3.0", + "@vue/shared": "^3.3.0", + "minimatch": "^9.0.0", + "muggle-string": "^0.3.1", + "vue-template-compiler": "^2.7.14" + }, + "peerDependencies": { + "typescript": "*" + }, + "peerDependenciesMeta": { + "typescript": { + "optional": true + } + } + }, + "node_modules/@vue/language-core/node_modules/brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "dependencies": { + "balanced-match": "^1.0.0" + } + }, + "node_modules/@vue/language-core/node_modules/minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "dependencies": { + "brace-expansion": "^2.0.1" + }, + "engines": { + "node": ">=16 || 14 >=14.17" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "dependencies": { + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "dependencies": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "node_modules/@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "dependencies": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "dependencies": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "node_modules/@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "dependencies": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + }, + "peerDependencies": { + "vue": "3.3.4" + } + }, + "node_modules/@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, + "node_modules/@vue/typescript": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.4.tgz", + "integrity": "sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==", + "dev": true, + "dependencies": { + "@volar/typescript": "~1.8.0", + "@vue/language-core": "1.8.4" + } + }, + "node_modules/@vuelidate/core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz", + "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/core/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.3.tgz", + "integrity": "sha512-P4WtmczPRbwmVihKlSAs3pIjIil4xjHW+Qoi58MCw7vnKuYr/IHarOFkwp0K8eIIqiQqY4URM5Gb4aeDjna6Yw==", + "dependencies": { + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^2.0.0 || >=3.0.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vuelidate/validators/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/core": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.2.1.tgz", + "integrity": "sha512-c441bfMbkAwTNwVRHQ0zdYZNETK//P84rC01aP2Uy/aRFCiie9NE/k9KdIXbno0eDYP5NPUuWv0aA/I4Unr/7w==", + "dependencies": { + "@types/web-bluetooth": "^0.0.17", + "@vueuse/metadata": "10.2.1", + "@vueuse/shared": "10.2.1", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/core/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/metadata": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.2.1.tgz", + "integrity": "sha512-3Gt68mY/i6bQvFqx7cuGBzrCCQu17OBaGWS5JdwISpMsHnMKKjC2FeB5OAfMcCQ0oINfADP3i9A4PPRo0peHdQ==", + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/router": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/router/-/router-10.2.1.tgz", + "integrity": "sha512-H/1T4fLzMmeBNEmcXlbqk6AEp0HQpzf+0eeNJ6fGrs3RWClE2i3nYEFbtxfQeSm/7nZ6nf/UhgahzUQdyMhIwQ==", + "dependencies": { + "@vueuse/shared": "10.2.1", + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "vue-router": ">=4.0.0-rc.1" + } + }, + "node_modules/@vueuse/router/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/@vueuse/shared": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.2.1.tgz", + "integrity": "sha512-QWHq2bSuGptkcxx4f4M/fBYC3Y8d3M2UYyLsyzoPgEoVzJURQ0oJeWXu79OiLlBb8gTKkqe4mO85T/sf39mmiw==", + "dependencies": { + "vue-demi": ">=0.14.5" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "node_modules/@vueuse/shared/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "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, + "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, + "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, + "dependencies": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + }, + "funding": { + "type": "github", + "url": "https://github.com/sponsors/epoberezkin" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "node_modules/anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "dependencies": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "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 + }, + "node_modules/array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "node_modules/autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/autoprefixer" + } + ], + "dependencies": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + }, + "bin": { + "autoprefixer": "bin/autoprefixer" + }, + "engines": { + "node": "^10 || ^12 || >=14" + }, + "peerDependencies": { + "postcss": "^8.1.0" + } + }, + "node_modules/available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "dependencies": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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 + }, + "node_modules/base32-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-2.0.0.tgz", + "integrity": "sha512-mlmkfc2WqdDtMl/id4qm3A7RjW6jxcbAoMjdRmsPiwQP0ufD4oXItYMnPgVHe80lnAIy+1xwzhHE1s4FoIceSw==", + "dependencies": { + "to-data-view": "^2.0.0" + }, + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "node_modules/binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true, + "engines": { + "node": ">=8" + } + }, + "node_modules/boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": 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, + "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/browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + }, + "bin": { + "browserslist": "cli.js" + }, + "engines": { + "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7" + } + }, + "node_modules/builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=6" + } + }, + "node_modules/camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "dependencies": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + } + }, + "node_modules/camel-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/caniuse-lite": { + "version": "1.0.30001514", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz", + "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/caniuse-lite" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ] + }, + "node_modules/capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/capital-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "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, + "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/change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "dependencies": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/change-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "dependencies": { + "is-regex": "^1.0.3" + } + }, + "node_modules/chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "funding": [ + { + "type": "individual", + "url": "https://paulmillr.com/funding/" + } + ], + "dependencies": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "engines": { + "node": ">= 8.10.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "node_modules/chokidar/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/cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "peer": true, + "dependencies": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "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 + }, + "node_modules/constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + } + }, + "node_modules/constant-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==", + "engines": { + "node": ">= 0.6" + } + }, + "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, + "dependencies": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true, + "bin": { + "cssesc": "bin/cssesc" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "node_modules/de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "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/decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "dependencies": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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 + }, + "node_modules/define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "dependencies": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "node_modules/diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true, + "engines": { + "node": ">=0.3.1" + } + }, + "node_modules/dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "peer": true + }, + "node_modules/dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "node_modules/doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "dependencies": { + "esutils": "^2.0.2" + }, + "engines": { + "node": ">=6.0.0" + } + }, + "node_modules/dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/dot-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/motdotla/dotenv?sponsor=1" + } + }, + "node_modules/electron-to-chromium": { + "version": "1.4.454", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz", + "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==", + "dev": true + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "node_modules/encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "peer": true + }, + "node_modules/es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/esbuild": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz", + "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==", + "dev": true, + "hasInstallScript": true, + "bin": { + "esbuild": "bin/esbuild" + }, + "engines": { + "node": ">=12" + }, + "optionalDependencies": { + "@esbuild/android-arm": "0.18.11", + "@esbuild/android-arm64": "0.18.11", + "@esbuild/android-x64": "0.18.11", + "@esbuild/darwin-arm64": "0.18.11", + "@esbuild/darwin-x64": "0.18.11", + "@esbuild/freebsd-arm64": "0.18.11", + "@esbuild/freebsd-x64": "0.18.11", + "@esbuild/linux-arm": "0.18.11", + "@esbuild/linux-arm64": "0.18.11", + "@esbuild/linux-ia32": "0.18.11", + "@esbuild/linux-loong64": "0.18.11", + "@esbuild/linux-mips64el": "0.18.11", + "@esbuild/linux-ppc64": "0.18.11", + "@esbuild/linux-riscv64": "0.18.11", + "@esbuild/linux-s390x": "0.18.11", + "@esbuild/linux-x64": "0.18.11", + "@esbuild/netbsd-x64": "0.18.11", + "@esbuild/openbsd-x64": "0.18.11", + "@esbuild/sunos-x64": "0.18.11", + "@esbuild/win32-arm64": "0.18.11", + "@esbuild/win32-ia32": "0.18.11", + "@esbuild/win32-x64": "0.18.11" + } + }, + "node_modules/escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.6.0", + "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", + "import-fresh": "^3.0.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", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + }, + "bin": { + "eslint": "bin/eslint.js" + }, + "engines": { + "node": "^12.22.0 || ^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://opencollective.com/eslint" + } + }, + "node_modules/eslint-plugin-vue": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", + "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", + "dev": true, + "dependencies": { + "@eslint-community/eslint-utils": "^4.3.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.3.0", + "xml-name-validator": "^4.0.0" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "peerDependencies": { + "eslint": "^6.2.0 || ^7.0.0 || ^8.0.0" + } + }, + "node_modules/eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": 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.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "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.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "dev": 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/esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true, + "bin": { + "esparse": "bin/esparse.js", + "esvalidate": "bin/esvalidate.js" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/esprima-extract-comments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz", + "integrity": "sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==", + "dev": true, + "dependencies": { + "esprima": "^4.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "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, + "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, + "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, + "engines": { + "node": ">=4.0" + } + }, + "node_modules/estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "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, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/extract-comments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/extract-comments/-/extract-comments-1.1.0.tgz", + "integrity": "sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==", + "dev": true, + "dependencies": { + "esprima-extract-comments": "^1.1.0", + "parse-code-context": "^1.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "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 + }, + "node_modules/fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "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 + }, + "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 + }, + "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, + "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, + "dependencies": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "dependencies": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + }, + "engines": { + "node": "^10.12.0 || >=12.0.0" + } + }, + "node_modules/flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "node_modules/follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "dependencies": { + "is-callable": "^1.1.3" + } + }, + "node_modules/form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true, + "engines": { + "node": "*" + }, + "funding": { + "type": "patreon", + "url": "https://www.patreon.com/infusion" + } + }, + "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 + }, + "node_modules/fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "hasInstallScript": true, + "optional": true, + "os": [ + "darwin" + ], + "engines": { + "node": "^8.16.0 || ^10.6.0 || >=11.0.0" + } + }, + "node_modules/function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "node_modules/functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "peer": true, + "engines": { + "node": "6.* || 8.* || >= 10.*" + } + }, + "node_modules/get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "dependencies": { + "is-glob": "^4.0.3" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "dependencies": { + "type-fest": "^0.20.2" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "dependencies": { + "function-bind": "^1.1.1" + }, + "engines": { + "node": ">= 0.4.0" + } + }, + "node_modules/has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true, + "bin": { + "he": "bin/he" + } + }, + "node_modules/header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "dependencies": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/header-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "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/immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "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, + "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, + "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, + "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 + }, + "node_modules/internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "dependencies": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "dependencies": { + "has-bigints": "^1.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "dependencies": { + "binary-extensions": "^2.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "dependencies": { + "has": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "dependencies": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + } + }, + "node_modules/is-expression/node_modules/acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true, + "bin": { + "acorn": "bin/acorn" + }, + "engines": { + "node": ">=0.4.0" + } + }, + "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-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true, + "engines": { + "node": ">=8" + } + }, + "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-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "dependencies": { + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "dependencies": { + "has-symbols": "^1.0.2" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "node_modules/isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "node_modules/jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "dev": true, + "bin": { + "jiti": "bin/jiti.js" + } + }, + "node_modules/jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==", + "funding": { + "url": "https://github.com/sponsors/panva" + } + }, + "node_modules/js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "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, + "dependencies": { + "argparse": "^2.0.1" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/json-editor-vue": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/json-editor-vue/-/json-editor-vue-0.10.6.tgz", + "integrity": "sha512-jtKEmiCTLuri5uBkyb4qUrR6QwJgBJvBrYlFeY0L3NBE4RWDUlKy/swCcX1p32KmXKLjezzMSOvEEeAmQ6hjTw==", + "dependencies": { + "lodash-es": "latest", + "vue-demi": "latest", + "vue-global-config": "latest" + }, + "peerDependencies": { + "@vue/composition-api": ">=1", + "vanilla-jsoneditor": ">=0", + "vue": "2||3" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/json-editor-vue/node_modules/vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/json-editor-vue/node_modules/vue-global-config": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/vue-global-config/-/vue-global-config-0.4.0.tgz", + "integrity": "sha512-UoFouTLUYGuHEEJFRgl29dzD0dDYtAEPRMDe8S0e08aB8vgfuD0ybW5WdPRTfTRChceHCn4eMQH2jFFVyi3Djw==", + "dependencies": { + "change-case": "^4.1.2", + "lodash-es": "^4.17.21", + "vue-demi": "^0.13.11" + }, + "peerDependencies": { + "@vue/composition-api": ">=1", + "vue": "2||3" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": true + } + } + }, + "node_modules/json-editor-vue/node_modules/vue-global-config/node_modules/vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "hasInstallScript": true, + "bin": { + "vue-demi-fix": "bin/vue-demi-fix.js", + "vue-demi-switch": "bin/vue-demi-switch.js" + }, + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + }, + "peerDependencies": { + "@vue/composition-api": "^1.0.0-rc.1", + "vue": "^3.0.0-0 || ^2.6.0" + }, + "peerDependenciesMeta": { + "@vue/composition-api": { + "optional": 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 + }, + "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 + }, + "node_modules/json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true, + "bin": { + "json5": "lib/cli.js" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/jssha": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.0.tgz", + "integrity": "sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==", + "engines": { + "node": "*" + } + }, + "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, + "dependencies": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + }, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true, + "engines": { + "node": ">=10" + } + }, + "node_modules/lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "node_modules/local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true, + "engines": { + "node": ">=14" + }, + "funding": { + "url": "https://github.com/sponsors/antfu" + } + }, + "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, + "dependencies": { + "p-locate": "^5.0.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "node_modules/lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "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 + }, + "node_modules/lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/lower-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "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/magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "dependencies": { + "@jridgewell/sourcemap-codec": "^1.4.15" + }, + "engines": { + "node": ">=12" + } + }, + "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/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.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, + "dependencies": { + "brace-expansion": "^1.1.7" + }, + "engines": { + "node": "*" + } + }, + "node_modules/minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "dependencies": { + "minimist": "^1.2.6" + }, + "bin": { + "mkdirp": "bin/cmd.js" + } + }, + "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/muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, + "node_modules/mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "node_modules/nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "bin": { + "nanoid": "bin/nanoid.cjs" + }, + "engines": { + "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" + } + }, + "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/no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "dependencies": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + } + }, + "node_modules/no-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "node_modules/normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "dependencies": { + "boolbase": "^1.0.0" + }, + "funding": { + "url": "https://github.com/fb55/nth-check?sponsor=1" + } + }, + "node_modules/object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "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/otpauth": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.3.tgz", + "integrity": "sha512-lWy9GE2ASTgOwhH1dQsZwOIcVl2HopXNnTTtBXuhO4VZJ9AXT8Zx1ifTGaFUiXVFOzbHD/W4hDqMd/ZRDLkJEw==", + "dependencies": { + "jssha": "~3.3.0" + }, + "funding": { + "url": "https://github.com/hectorm/otpauth?sponsor=1" + } + }, + "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, + "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, + "dependencies": { + "p-limit": "^3.0.2" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "peer": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/param-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "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, + "dependencies": { + "callsites": "^3.0.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/parse-code-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-code-context/-/parse-code-context-1.0.0.tgz", + "integrity": "sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA==", + "dev": true, + "engines": { + "node": ">=6" + } + }, + "node_modules/pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/pascal-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/path-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==", + "engines": { + "node": ">=8" + } + }, + "node_modules/path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": 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, + "engines": { + "node": ">=8" + } + }, + "node_modules/path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "node_modules/picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "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/pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true, + "engines": { + "node": ">= 6" + } + }, + "node_modules/pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "peer": true, + "engines": { + "node": ">=10.13.0" + } + }, + "node_modules/postcss": { + "version": "8.4.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", + "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/postcss" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + }, + "engines": { + "node": "^10 || ^12 || >=14" + } + }, + "node_modules/postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "dependencies": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + }, + "engines": { + "node": ">=14.0.0" + }, + "peerDependencies": { + "postcss": "^8.0.0" + } + }, + "node_modules/postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "dependencies": { + "camelcase-css": "^2.0.1" + }, + "engines": { + "node": "^12 || ^14 || >= 16" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.4.21" + } + }, + "node_modules/postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "dependencies": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + }, + "engines": { + "node": ">= 14" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": ">=8.0.9", + "ts-node": ">=9.0.0" + }, + "peerDependenciesMeta": { + "postcss": { + "optional": true + }, + "ts-node": { + "optional": true + } + } + }, + "node_modules/postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "dependencies": { + "postcss-selector-parser": "^6.0.11" + }, + "engines": { + "node": ">=12.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/postcss/" + }, + "peerDependencies": { + "postcss": "^8.2.14" + } + }, + "node_modules/postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "dependencies": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "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, + "engines": { + "node": ">= 0.8.0" + } + }, + "node_modules/prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true, + "bin": { + "prettier": "bin-prettier.js" + }, + "engines": { + "node": ">=10.13.0" + }, + "funding": { + "url": "https://github.com/prettier/prettier?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "node_modules/pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==", + "dev": true + }, + "node_modules/pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "dependencies": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.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, + "engines": { + "node": ">=6" + } + }, + "node_modules/qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "peer": true, + "dependencies": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + }, + "bin": { + "qrcode": "bin/qrcode" + }, + "engines": { + "node": ">=10.13.0" + } + }, + "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/read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "dependencies": { + "pify": "^2.3.0" + } + }, + "node_modules/readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "dependencies": { + "picomatch": "^2.2.1" + }, + "engines": { + "node": ">=8.10.0" + } + }, + "node_modules/regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "peer": true, + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "peer": true + }, + "node_modules/resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "dependencies": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + }, + "bin": { + "resolve": "bin/resolve" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "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, + "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, + "dependencies": { + "glob": "^7.1.3" + }, + "bin": { + "rimraf": "bin.js" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "node_modules/rollup": { + "version": "3.26.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", + "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==", + "dev": true, + "bin": { + "rollup": "dist/bin/rollup" + }, + "engines": { + "node": ">=14.18.0", + "npm": ">=8.0.0" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + } + }, + "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/sass": { + "version": "1.63.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", + "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "dev": true, + "dependencies": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + }, + "bin": { + "sass": "sass.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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/sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "dependencies": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + } + }, + "node_modules/sentence-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "peer": true + }, + "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, + "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, + "engines": { + "node": ">=8" + } + }, + "node_modules/showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "dependencies": { + "commander": "^9.0.0" + }, + "bin": { + "showdown": "bin/showdown.js" + }, + "funding": { + "type": "individual", + "url": "https://www.paypal.me/tiviesantos" + } + }, + "node_modules/showdown/node_modules/commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==", + "engines": { + "node": "^12.20.0 || >=14" + } + }, + "node_modules/side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "dependencies": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "dependencies": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + } + }, + "node_modules/snake-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", + "engines": { + "node": ">=0.10.0" + } + }, + "node_modules/sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "node_modules/stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "dependencies": { + "internal-slot": "^1.0.4" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "dependencies": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "bin": { + "sucrase": "bin/sucrase", + "sucrase-node": "bin/sucrase-node" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/sucrase/node_modules/glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "dependencies": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + }, + "engines": { + "node": "*" + }, + "funding": { + "url": "https://github.com/sponsors/isaacs" + } + }, + "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, + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dev": true, + "dependencies": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + }, + "bin": { + "tailwind": "lib/cli.js", + "tailwindcss": "lib/cli.js" + }, + "engines": { + "node": ">=14.0.0" + } + }, + "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 + }, + "node_modules/thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "dependencies": { + "any-promise": "^1.0.0" + } + }, + "node_modules/thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "dependencies": { + "thenify": ">= 3.1.0 < 4" + }, + "engines": { + "node": ">=0.8" + } + }, + "node_modules/to-data-view": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-2.0.0.tgz", + "integrity": "sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA==", + "engines": { + "node": "^12.20.0 || ^14.13.1 || >=16.0.0" + } + }, + "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-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "node_modules/tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "node_modules/tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "deprecated": "TSLint has been deprecated in favor of ESLint. Please see https://github.com/palantir/tslint/issues/4534 for more information.", + "dev": true, + "dependencies": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "bin": { + "tslint": "bin/tslint" + }, + "engines": { + "node": ">=4.8.0" + }, + "peerDependencies": { + "typescript": ">=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >=3.0.0-dev || >= 3.1.0-dev || >= 3.2.0-dev || >= 4.0.0-dev" + } + }, + "node_modules/tslint/node_modules/ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "dependencies": { + "color-convert": "^1.9.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "dependencies": { + "sprintf-js": "~1.0.2" + } + }, + "node_modules/tslint/node_modules/chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "dependencies": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "dependencies": { + "color-name": "1.1.3" + } + }, + "node_modules/tslint/node_modules/color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "node_modules/tslint/node_modules/commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "node_modules/tslint/node_modules/escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true, + "engines": { + "node": ">=0.8.0" + } + }, + "node_modules/tslint/node_modules/has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true, + "engines": { + "node": ">=4" + } + }, + "node_modules/tslint/node_modules/js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "dependencies": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + }, + "bin": { + "js-yaml": "bin/js-yaml.js" + } + }, + "node_modules/tslint/node_modules/semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true, + "bin": { + "semver": "bin/semver" + } + }, + "node_modules/tslint/node_modules/supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "dependencies": { + "has-flag": "^3.0.0" + }, + "engines": { + "node": ">=4" + } + }, + "node_modules/tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "dependencies": { + "tslib": "^1.8.1" + }, + "peerDependencies": { + "typescript": ">=2.1.0 || >=2.1.0-dev || >=2.2.0-dev || >=2.3.0-dev || >=2.4.0-dev || >=2.5.0-dev || >=2.6.0-dev || >=2.7.0-dev || >=2.8.0-dev || >=2.9.0-dev || >= 3.0.0-dev || >= 3.1.0-dev" + } + }, + "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, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=14.17" + } + }, + "node_modules/universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "dependencies": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, + "node_modules/update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "funding": [ + { + "type": "opencollective", + "url": "https://opencollective.com/browserslist" + }, + { + "type": "tidelift", + "url": "https://tidelift.com/funding/github/npm/browserslist" + }, + { + "type": "github", + "url": "https://github.com/sponsors/ai" + } + ], + "dependencies": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + }, + "bin": { + "update-browserslist-db": "cli.js" + }, + "peerDependencies": { + "browserslist": ">= 4.21.0" + } + }, + "node_modules/upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "dependencies": { + "tslib": "^2.0.3" + } + }, + "node_modules/upper-case-first/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "node_modules/upper-case/node_modules/tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + }, + "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, + "dependencies": { + "punycode": "^2.1.0" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "node_modules/vanilla-jsoneditor": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-0.17.8.tgz", + "integrity": "sha512-DP9GP/IBQjYOnC820CYoFuXs3vgrL+zdGGp1X83qFirSkRWPeP+6zB/14a0LYhvomQNNezes5Gwel89MKc4Qbg==" + }, + "node_modules/vite": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.2.tgz", + "integrity": "sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA==", + "dev": true, + "dependencies": { + "esbuild": "^0.18.10", + "postcss": "^8.4.24", + "rollup": "^3.25.2" + }, + "bin": { + "vite": "bin/vite.js" + }, + "engines": { + "node": "^14.18.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/vitejs/vite?sponsor=1" + }, + "optionalDependencies": { + "fsevents": "~2.3.2" + }, + "peerDependencies": { + "@types/node": ">= 14", + "less": "*", + "lightningcss": "^1.21.0", + "sass": "*", + "stylus": "*", + "sugarss": "*", + "terser": "^5.4.0" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + }, + "less": { + "optional": true + }, + "lightningcss": { + "optional": true + }, + "sass": { + "optional": true + }, + "stylus": { + "optional": true + }, + "sugarss": { + "optional": true + }, + "terser": { + "optional": true + } + } + }, + "node_modules/vite-plugin-pages": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pages/-/vite-plugin-pages-0.31.0.tgz", + "integrity": "sha512-fw3onBfVTXQI7rOzAbSZhmfwvk50+3qNnGZpERjmD93c8nEjrGLyd53eFXYMxcJV4KA1vzi4qIHt2+6tS4dEMw==", + "dev": true, + "dependencies": { + "@types/debug": "^4.1.8", + "debug": "^4.3.4", + "deep-equal": "^2.2.1", + "extract-comments": "^1.1.0", + "fast-glob": "^3.2.12", + "json5": "^2.2.3", + "local-pkg": "^0.4.3", + "picocolors": "^1.0.0", + "yaml": "^2.3.1" + }, + "peerDependencies": { + "@vue/compiler-sfc": "^2.7.0 || ^3.0.0", + "vite": "^2.0.0 || ^3.0.0-0 || ^4.0.0" + }, + "peerDependenciesMeta": { + "@vue/compiler-sfc": { + "optional": true + } + } + }, + "node_modules/vls": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/vls/-/vls-0.8.5.tgz", + "integrity": "sha512-61kbdO2COZWBMC4wq59QfDdev9ruXd0226f57DFJTFpFXv85S+qnHakQlAmbSYFFLGKcx95HB2UjnuQh4YRwFA==", + "dev": true, + "dependencies": { + "eslint": "^8.34.0", + "eslint-plugin-vue": "^9.9.0", + "prettier": "^2.8.4", + "pug-lexer": "^5.0.1", + "tslint": "6.1.3", + "typescript": "^4.9.5" + }, + "bin": { + "vls": "bin/vls" + }, + "engines": { + "node": ">=10" + } + }, + "node_modules/vls/node_modules/typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true, + "bin": { + "tsc": "bin/tsc", + "tsserver": "bin/tsserver" + }, + "engines": { + "node": ">=4.2.0" + } + }, + "node_modules/vscode-html-languageservice": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.0.6.tgz", + "integrity": "sha512-gCixNg6fjPO7+kwSMBAVXcwDRHdjz1WOyNfI0n5Wx0J7dfHG8ggb3zD1FI8E2daTZrwS1cooOiSoc1Xxph4qRQ==", + "dev": true, + "dependencies": { + "@vscode/l10n": "^0.0.14", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.0.7" + } + }, + "node_modules/vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", + "dev": true + }, + "node_modules/vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==", + "dev": true + }, + "node_modules/vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, + "node_modules/vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "dependencies": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "node_modules/vue-eslint-parser": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz", + "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==", + "dev": true, + "dependencies": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + }, + "engines": { + "node": "^14.17.0 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/sponsors/mysticatea" + }, + "peerDependencies": { + "eslint": ">=6.0.0" + } + }, + "node_modules/vue-router": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", + "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "dependencies": { + "@vue/devtools-api": "^6.5.0" + }, + "funding": { + "url": "https://github.com/sponsors/posva" + }, + "peerDependencies": { + "vue": "^3.2.0" + } + }, + "node_modules/vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "dependencies": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "node_modules/vue-tsc": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.4.tgz", + "integrity": "sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==", + "dev": true, + "dependencies": { + "@vue/language-core": "1.8.4", + "@vue/typescript": "1.8.4", + "semver": "^7.3.8" + }, + "bin": { + "vue-tsc": "bin/vue-tsc.js" + }, + "peerDependencies": { + "typescript": "*" + } + }, + "node_modules/vue3-otp-input": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vue3-otp-input/-/vue3-otp-input-0.4.1.tgz", + "integrity": "sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==", + "engines": { + "node": ">=16.0.0", + "npm": ">=8.0.0" + }, + "peerDependencies": { + "vue": "^3.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, + "dependencies": { + "isexe": "^2.0.0" + }, + "bin": { + "node-which": "bin/node-which" + }, + "engines": { + "node": ">= 8" + } + }, + "node_modules/which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "dependencies": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "dependencies": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "peer": true + }, + "node_modules/which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "dependencies": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "peer": true, + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "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 + }, + "node_modules/xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true, + "engines": { + "node": ">=12" + } + }, + "node_modules/y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "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/yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true, + "engines": { + "node": ">= 14" + } + }, + "node_modules/yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "peer": true, + "dependencies": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "peer": true, + "dependencies": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + }, + "engines": { + "node": ">=6" + } + }, + "node_modules/yargs/node_modules/find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "peer": true, + "dependencies": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "peer": true, + "dependencies": { + "p-locate": "^4.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yargs/node_modules/p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "dependencies": { + "p-try": "^2.0.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/yargs/node_modules/p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "peer": true, + "dependencies": { + "p-limit": "^2.2.0" + }, + "engines": { + "node": ">=8" + } + }, + "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, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + }, + "dependencies": { + "@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 + }, + "@alloc/quick-lru": { + "version": "5.2.0", + "resolved": "https://registry.npmjs.org/@alloc/quick-lru/-/quick-lru-5.2.0.tgz", + "integrity": "sha512-UrcABB+4bUrFABwbluTIBErXwvbsU/V7TZWfmbgJfbkwiBuziS9gxdODUyuiecfdGQ85jglMW6juS3+z5TsKLw==", + "dev": true + }, + "@babel/code-frame": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.22.5.tgz", + "integrity": "sha512-Xmwn266vad+6DAqEB2A6V/CcZVp62BbwVmcOJc2RPuwih1kw02TjQvWVWlcKGbBPd+8/0V5DEkOcizRGYsspYQ==", + "dev": true, + "requires": { + "@babel/highlight": "^7.22.5" + } + }, + "@babel/helper-validator-identifier": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.22.5.tgz", + "integrity": "sha512-aJXu+6lErq8ltp+JhkJUfk1MTGyuA4v7f3pA+BJ5HLfNC6nAQ0Cpi9uOquUj8Hehg0aUiHzWQbOVJGao6ztBAQ==", + "dev": true + }, + "@babel/highlight": { + "version": "7.22.5", + "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.22.5.tgz", + "integrity": "sha512-BSKlD1hgnedS5XRnGOljZawtag7H1yPfQp0tdNJCHoH6AZ+Pcm9VvkrK59/Yy593Ypg0zMxH2BxD1VPYUQ7UIw==", + "dev": true, + "requires": { + "@babel/helper-validator-identifier": "^7.22.5", + "chalk": "^2.0.0", + "js-tokens": "^4.0.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "@babel/parser": { + "version": "7.22.7", + "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.22.7.tgz", + "integrity": "sha512-7NF8pOkHP5o2vpmGgNGcfAeCvOYhGLyA3Z4eBQkT1RJlWu47n63bCs93QfJ2hIAFCil7L5P2IWhs1oToVgrL0Q==" + }, + "@chenfengyuan/vue-qrcode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@chenfengyuan/vue-qrcode/-/vue-qrcode-2.0.0.tgz", + "integrity": "sha512-33Cfr0zjbc3Dd8d5b1IgzXRAgXH0c2Gv19VI4snS25V/x9Z41eg769tC+Us1x+vqgQQhgD5YUjLnkpkrQfeMSw==", + "requires": {} + }, + "@esbuild/android-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.11.tgz", + "integrity": "sha512-q4qlUf5ucwbUJZXF5tEQ8LF7y0Nk4P58hOsGk3ucY0oCwgQqAnqXVbUuahCddVHfrxmpyewRpiTHwVHIETYu7Q==", + "dev": true, + "optional": true + }, + "@esbuild/android-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.11.tgz", + "integrity": "sha512-snieiq75Z1z5LJX9cduSAjUr7vEI1OdlzFPMw0HH5YI7qQHDd3qs+WZoMrWYDsfRJSq36lIA6mfZBkvL46KoIw==", + "dev": true, + "optional": true + }, + "@esbuild/android-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.11.tgz", + "integrity": "sha512-iPuoxQEV34+hTF6FT7om+Qwziv1U519lEOvekXO9zaMMlT9+XneAhKL32DW3H7okrCOBQ44BMihE8dclbZtTuw==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.11.tgz", + "integrity": "sha512-Gm0QkI3k402OpfMKyQEEMG0RuW2LQsSmI6OeO4El2ojJMoF5NLYb3qMIjvbG/lbMeLOGiW6ooU8xqc+S0fgz2w==", + "dev": true, + "optional": true + }, + "@esbuild/darwin-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.11.tgz", + "integrity": "sha512-N15Vzy0YNHu6cfyDOjiyfJlRJCB/ngKOAvoBf1qybG3eOq0SL2Lutzz9N7DYUbb7Q23XtHPn6lMDF6uWbGv9Fw==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.11.tgz", + "integrity": "sha512-atEyuq6a3omEY5qAh5jIORWk8MzFnCpSTUruBgeyN9jZq1K/QI9uke0ATi3MHu4L8c59CnIi4+1jDKMuqmR71A==", + "dev": true, + "optional": true + }, + "@esbuild/freebsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.11.tgz", + "integrity": "sha512-XtuPrEfBj/YYYnAAB7KcorzzpGTvOr/dTtXPGesRfmflqhA4LMF0Gh/n5+a9JBzPuJ+CGk17CA++Hmr1F/gI0Q==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.11.tgz", + "integrity": "sha512-Idipz+Taso/toi2ETugShXjQ3S59b6m62KmLHkJlSq/cBejixmIydqrtM2XTvNCywFl3VC7SreSf6NV0i6sRyg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.11.tgz", + "integrity": "sha512-c6Vh2WS9VFKxKZ2TvJdA7gdy0n6eSy+yunBvv4aqNCEhSWVor1TU43wNRp2YLO9Vng2G+W94aRz+ILDSwAiYog==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.11.tgz", + "integrity": "sha512-S3hkIF6KUqRh9n1Q0dSyYcWmcVa9Cg+mSoZEfFuzoYXXsk6196qndrM+ZiHNwpZKi3XOXpShZZ+9dfN5ykqjjw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-loong64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.11.tgz", + "integrity": "sha512-MRESANOoObQINBA+RMZW+Z0TJWpibtE7cPFnahzyQHDCA9X9LOmGh68MVimZlM9J8n5Ia8lU773te6O3ILW8kw==", + "dev": true, + "optional": true + }, + "@esbuild/linux-mips64el": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.11.tgz", + "integrity": "sha512-qVyPIZrXNMOLYegtD1u8EBccCrBVshxMrn5MkuFc3mEVsw7CCQHaqZ4jm9hbn4gWY95XFnb7i4SsT3eflxZsUg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-ppc64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.11.tgz", + "integrity": "sha512-T3yd8vJXfPirZaUOoA9D2ZjxZX4Gr3QuC3GztBJA6PklLotc/7sXTOuuRkhE9W/5JvJP/K9b99ayPNAD+R+4qQ==", + "dev": true, + "optional": true + }, + "@esbuild/linux-riscv64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.11.tgz", + "integrity": "sha512-evUoRPWiwuFk++snjH9e2cAjF5VVSTj+Dnf+rkO/Q20tRqv+644279TZlPK8nUGunjPAtQRCj1jQkDAvL6rm2w==", + "dev": true, + "optional": true + }, + "@esbuild/linux-s390x": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.11.tgz", + "integrity": "sha512-/SlRJ15XR6i93gRWquRxYCfhTeC5PdqEapKoLbX63PLCmAkXZHY2uQm2l9bN0oPHBsOw2IswRZctMYS0MijFcg==", + "dev": true, + "optional": true + }, + "@esbuild/linux-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.11.tgz", + "integrity": "sha512-xcncej+wF16WEmIwPtCHi0qmx1FweBqgsRtEL1mSHLFR6/mb3GEZfLQnx+pUDfRDEM4DQF8dpXIW7eDOZl1IbA==", + "dev": true, + "optional": true + }, + "@esbuild/netbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.11.tgz", + "integrity": "sha512-aSjMHj/F7BuS1CptSXNg6S3M4F3bLp5wfFPIJM+Km2NfIVfFKhdmfHF9frhiCLIGVzDziggqWll0B+9AUbud/Q==", + "dev": true, + "optional": true + }, + "@esbuild/openbsd-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.11.tgz", + "integrity": "sha512-tNBq+6XIBZtht0xJGv7IBB5XaSyvYPCm1PxJ33zLQONdZoLVM0bgGqUrXnJyiEguD9LU4AHiu+GCXy/Hm9LsdQ==", + "dev": true, + "optional": true + }, + "@esbuild/sunos-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.11.tgz", + "integrity": "sha512-kxfbDOrH4dHuAAOhr7D7EqaYf+W45LsAOOhAet99EyuxxQmjbk8M9N4ezHcEiCYPaiW8Dj3K26Z2V17Gt6p3ng==", + "dev": true, + "optional": true + }, + "@esbuild/win32-arm64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.11.tgz", + "integrity": "sha512-Sh0dDRyk1Xi348idbal7lZyfSkjhJsdFeuC13zqdipsvMetlGiFQNdO+Yfp6f6B4FbyQm7qsk16yaZk25LChzg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-ia32": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.11.tgz", + "integrity": "sha512-o9JUIKF1j0rqJTFbIoF4bXj6rvrTZYOrfRcGyL0Vm5uJ/j5CkBD/51tpdxe9lXEDouhRgdr/BYzUrDOvrWwJpg==", + "dev": true, + "optional": true + }, + "@esbuild/win32-x64": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.11.tgz", + "integrity": "sha512-rQI4cjLHd2hGsM1LqgDI7oOCYbQ6IBOVsX9ejuRMSze0GqXUG2ekwiKkiBU1pRGSeCqFFHxTrcEydB2Hyoz9CA==", + "dev": true, + "optional": true + }, + "@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, + "requires": { + "eslint-visitor-keys": "^3.3.0" + } + }, + "@eslint-community/regexpp": { + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/@eslint-community/regexpp/-/regexpp-4.5.1.tgz", + "integrity": "sha512-Z5ba73P98O1KUYCCJTUeVpja9RcGoMdncZ6T49FCUl2lN38JtCJ+3WgIDBv0AuY4WChU5PmtJmOCTlN6FZTFKQ==", + "dev": true + }, + "@eslint/eslintrc": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-2.1.0.tgz", + "integrity": "sha512-Lj7DECXqIVCqnqjjHMPna4vn6GJcMgul/wuS0je9OZ9gsL0zzDpKPVtcG1HaDVc+9y+qgXneTeUMbCqXJNpH1A==", + "dev": true, + "requires": { + "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" + } + }, + "@eslint/js": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/@eslint/js/-/js-8.44.0.tgz", + "integrity": "sha512-Ag+9YM4ocKQx9AarydN0KY2j0ErMHNIocPDrVo8zAE44xLTjEtz81OdR68/cydGtk6m6jDb5Za3r2useMzYmSw==", + "dev": true + }, + "@fontsource/nunito": { + "version": "5.0.5", + "resolved": "https://registry.npmjs.org/@fontsource/nunito/-/nunito-5.0.5.tgz", + "integrity": "sha512-y98oMry6UQM1L0kffWeRTTWu9PVBoFcX44l3vkHynSfk0BRHxxCDnm+uDPYYiIud7Qnwd3cZubw0Z4JwazXOWw==" + }, + "@fortawesome/fontawesome-common-types": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-common-types/-/fontawesome-common-types-6.4.0.tgz", + "integrity": "sha512-HNii132xfomg5QVZw0HwXXpN22s7VBHQBv9CeOu9tfJnhsWQNd2lmTNi8CSrnw5B+5YOmzu1UoPAyxaXsJ6RgQ==" + }, + "@fortawesome/fontawesome-svg-core": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/fontawesome-svg-core/-/fontawesome-svg-core-6.4.0.tgz", + "integrity": "sha512-Bertv8xOiVELz5raB2FlXDPKt+m94MQ3JgDfsVbrqNpLU9+UE2E18GKjLKw+d3XbeYPqg1pzyQKGsrzbw+pPaw==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-brands-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-brands-svg-icons/-/free-brands-svg-icons-6.4.0.tgz", + "integrity": "sha512-qvxTCo0FQ5k2N+VCXb/PZQ+QMhqRVM4OORiO6MXdG6bKolIojGU/srQ1ptvKk0JTbRgaJOfL2qMqGvBEZG7Z6g==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/free-solid-svg-icons": { + "version": "6.4.0", + "resolved": "https://registry.npmjs.org/@fortawesome/free-solid-svg-icons/-/free-solid-svg-icons-6.4.0.tgz", + "integrity": "sha512-kutPeRGWm8V5dltFP1zGjQOEAzaLZj4StdQhWVZnfGFCvAPVvHh8qk5bRrU4KXnRRRNni5tKQI9PBAdI6MP8nQ==", + "requires": { + "@fortawesome/fontawesome-common-types": "6.4.0" + } + }, + "@fortawesome/vue-fontawesome": { + "version": "3.0.3", + "resolved": "https://registry.npmjs.org/@fortawesome/vue-fontawesome/-/vue-fontawesome-3.0.3.tgz", + "integrity": "sha512-KCPHi9QemVXGMrfuwf3nNnNo129resAIQWut9QTAMXmXqL2ErABC6ohd2yY5Ipq0CLWNbKHk8TMdTXL/Zf3ZhA==", + "requires": {} + }, + "@headlessui/vue": { + "version": "1.7.14", + "resolved": "https://registry.npmjs.org/@headlessui/vue/-/vue-1.7.14.tgz", + "integrity": "sha512-aL9U9Sa7wdOzlrfjx6EjMIYNRCma5mngWcWzQBcHFwznpRZ8g/QZ/AYFtRDrZZUw22Ttttja4D7ZRXFwhONewA==", + "requires": {} + }, + "@humanwhocodes/config-array": { + "version": "0.11.10", + "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.10.tgz", + "integrity": "sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==", + "dev": true, + "requires": { + "@humanwhocodes/object-schema": "^1.2.1", + "debug": "^4.1.1", + "minimatch": "^3.0.5" + } + }, + "@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 + }, + "@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 + }, + "@jridgewell/gen-mapping": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.3.tgz", + "integrity": "sha512-HLhSWOLRi875zjjMG/r+Nv0oCW8umGb0BgEhyX3dDX3egwZtB8PqLnjz3yedt8R5StBrzcg4aBpnh8UA9D1BoQ==", + "dev": true, + "requires": { + "@jridgewell/set-array": "^1.0.1", + "@jridgewell/sourcemap-codec": "^1.4.10", + "@jridgewell/trace-mapping": "^0.3.9" + } + }, + "@jridgewell/resolve-uri": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz", + "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==", + "dev": true + }, + "@jridgewell/set-array": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz", + "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==", + "dev": true + }, + "@jridgewell/sourcemap-codec": { + "version": "1.4.15", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.15.tgz", + "integrity": "sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==" + }, + "@jridgewell/trace-mapping": { + "version": "0.3.18", + "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.18.tgz", + "integrity": "sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==", + "dev": true, + "requires": { + "@jridgewell/resolve-uri": "3.1.0", + "@jridgewell/sourcemap-codec": "1.4.14" + }, + "dependencies": { + "@jridgewell/sourcemap-codec": { + "version": "1.4.14", + "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz", + "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==", + "dev": true + } + } + }, + "@kyvg/vue3-notification": { + "version": "2.9.1", + "resolved": "https://registry.npmjs.org/@kyvg/vue3-notification/-/vue3-notification-2.9.1.tgz", + "integrity": "sha512-FsY8g25tQetr3etnarxHtCeNFKssH8sheFu13LyL2JJmOOel437QqKV5n4RBDDDTIo55iKgIVYXeojliXYdEhw==", + "requires": {} + }, + "@nodelib/fs.scandir": { + "version": "2.1.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz", + "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==", + "dev": true, + "requires": { + "@nodelib/fs.stat": "2.0.5", + "run-parallel": "^1.1.9" + } + }, + "@nodelib/fs.stat": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz", + "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==", + "dev": true + }, + "@nodelib/fs.walk": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz", + "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==", + "dev": true, + "requires": { + "@nodelib/fs.scandir": "2.1.5", + "fastq": "^1.6.0" + } + }, + "@types/cookie": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/@types/cookie/-/cookie-0.3.3.tgz", + "integrity": "sha512-LKVP3cgXBT9RYj+t+9FDKwS5tdI+rPBXaNSkma7hvqy35lc7mAokC2zsqWJH0LaqIt3B962nuYI77hsJoT1gow==" + }, + "@types/debug": { + "version": "4.1.8", + "resolved": "https://registry.npmjs.org/@types/debug/-/debug-4.1.8.tgz", + "integrity": "sha512-/vPO1EPOs306Cvhwv7KfVfYvOJqA/S/AXjaHQiJboCZzcNDb+TIJFN9/2C9DZ//ijSKWioNyUxD792QmDJ+HKQ==", + "dev": true, + "requires": { + "@types/ms": "*" + } + }, + "@types/lodash": { + "version": "4.14.195", + "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.195.tgz", + "integrity": "sha512-Hwx9EUgdwf2GLarOjQp5ZH8ZmblzcbTBC2wtQWNKARBSxM9ezRIAUpeDTgoQRAFB0+8CNWXVA9+MaSOzOF3nPg==", + "dev": true + }, + "@types/ms": { + "version": "0.7.31", + "resolved": "https://registry.npmjs.org/@types/ms/-/ms-0.7.31.tgz", + "integrity": "sha512-iiUgKzV9AuaEkZqkOLDIvlQiL6ltuZd9tGcW3gwpnX8JbuiuhFlEGmmFXEXkN50Cvq7Os88IY2v0dkDqXYWVgA==", + "dev": true + }, + "@types/showdown": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@types/showdown/-/showdown-2.0.1.tgz", + "integrity": "sha512-xdnAw2nFqomkaL0QdtEk0t7yz26UkaVPl4v1pYJvtE1T0fmfQEH3JaxErEhGByEAl3zUZrkNBlneuJp0WJGqEA==", + "dev": true + }, + "@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==" + }, + "@vitejs/plugin-vue": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/@vitejs/plugin-vue/-/plugin-vue-4.2.3.tgz", + "integrity": "sha512-R6JDUfiZbJA9cMiguQ7jxALsgiprjBeHL5ikpXfJCH62pPHtI+JdJ5xWj6Ev73yXSlYl86+blXn1kZHQ7uElxw==", + "dev": true, + "requires": {} + }, + "@vnuge/cmnext-admin": { + "version": "file:../lib/admin", + "requires": { + "@babel/types": "^7.x", + "@types/lodash": "^4.14.194", + "@typescript-eslint/eslint-plugin": "^5.59.1", + "axios": "^1.x", + "eslint": "^8.39.0", + "jose": "^4.13.x", + "universal-cookie": "^4.0.4" + } + }, + "@vnuge/vnlib.browser": { + "version": "https://www.vaughnnugent.com/public/resources/software/releases/vnlib-browser/v0.1.5.tgz", + "integrity": "sha512-O1iJ9F0e4y3dvGGQy/qI4FbeMAU7UUcJpA/4totci9GoslBL6ePRNS5NRpcLI3LPVH3fLbZTiOeH/L2F6ajV+g==", + "requires": {} + }, + "@volar-plugins/vetur": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/@volar-plugins/vetur/-/vetur-2.0.0.tgz", + "integrity": "sha512-RsubwRwnv497I8Dtru8gE/C6P+nmvmMLNnQnyLU3njTqO5Dm4KaBr6cbVl8iQOXHOxfBXzI3lmQSPWdsp3dXRA==", + "dev": true, + "requires": { + "vls": "^0.8.2", + "vscode-html-languageservice": "^5.0.4" + } + }, + "@volar/language-core": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/language-core/-/language-core-1.8.0.tgz", + "integrity": "sha512-ZHTvZPM3pEbOOuaq+ybNz5TQlHUqPQPK0G1+SonvApGq0e3qgGijjhtL5T7hsCtUEmxfix8FrAuCH14tMBOhTg==", + "dev": true, + "requires": { + "@volar/source-map": "1.8.0" + } + }, + "@volar/source-map": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/source-map/-/source-map-1.8.0.tgz", + "integrity": "sha512-d35aV0yFkIrkynRSKgrN5hgbMv6ekkFvcJsJGmOZ8UEjqLStto9zq7RSvpp6/PZ7/pa4Gn1f6K1qDt0bq0oUew==", + "dev": true, + "requires": { + "muggle-string": "^0.3.1" + } + }, + "@volar/typescript": { + "version": "1.8.0", + "resolved": "https://registry.npmjs.org/@volar/typescript/-/typescript-1.8.0.tgz", + "integrity": "sha512-T/U1XLLhXv6tNr40Awznfc6QZWizSL99t6M0DeXtIMbnvSCqjjCVRnwlsq+DK9C1RlO3k8+i0Z8iJn7O1GGtoA==", + "dev": true, + "requires": { + "@volar/language-core": "1.8.0" + } + }, + "@vscode/l10n": { + "version": "0.0.14", + "resolved": "https://registry.npmjs.org/@vscode/l10n/-/l10n-0.0.14.tgz", + "integrity": "sha512-/yrv59IEnmh655z1oeDnGcvMYwnEzNzHLgeYcQCkhYX0xBvYWrAuefoiLcPBUkMpJsb46bqQ6Yv4pwTTQ4d3Qg==", + "dev": true + }, + "@vue/compiler-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-core/-/compiler-core-3.3.4.tgz", + "integrity": "sha512-cquyDNvZ6jTbf/+x+AgM2Arrp6G4Dzbb0R64jiG804HRMfRiFXWI6kqUVqZ6ZR0bQhIoQjB4+2bhNtVwndW15g==", + "requires": { + "@babel/parser": "^7.21.3", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "source-map-js": "^1.0.2" + } + }, + "@vue/compiler-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-dom/-/compiler-dom-3.3.4.tgz", + "integrity": "sha512-wyM+OjOVpuUukIq6p5+nwHYtj9cFroz9cwkfmP9O1nzH68BenTTv0u7/ndggT8cIQlnBeOo6sUT/gvHcIkLA5w==", + "requires": { + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/compiler-sfc": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-sfc/-/compiler-sfc-3.3.4.tgz", + "integrity": "sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==", + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-ssr": "3.3.4", + "@vue/reactivity-transform": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0", + "postcss": "^8.1.10", + "source-map-js": "^1.0.2" + } + }, + "@vue/compiler-ssr": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/compiler-ssr/-/compiler-ssr-3.3.4.tgz", + "integrity": "sha512-m0v6oKpup2nMSehwA6Uuu+j+wEwcy7QmwMkVNVfrV9P2qE5KshC6RwOCq8fjGS/Eak/uNb8AaWekfiXxbBB6gQ==", + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/devtools-api": { + "version": "6.5.0", + "resolved": "https://registry.npmjs.org/@vue/devtools-api/-/devtools-api-6.5.0.tgz", + "integrity": "sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==" + }, + "@vue/language-core": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@vue/language-core/-/language-core-1.8.4.tgz", + "integrity": "sha512-pnNtNcJVfkGYluW0vsVO+Y1gyX+eA0voaS7+1JOhCp5zKeCaL/PAmGYOgfvwML62neL+2H8pnhY7sffmrGpEhw==", + "dev": true, + "requires": { + "@volar/language-core": "~1.8.0", + "@volar/source-map": "~1.8.0", + "@vue/compiler-dom": "^3.3.0", + "@vue/reactivity": "^3.3.0", + "@vue/shared": "^3.3.0", + "minimatch": "^9.0.0", + "muggle-string": "^0.3.1", + "vue-template-compiler": "^2.7.14" + }, + "dependencies": { + "brace-expansion": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz", + "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==", + "dev": true, + "requires": { + "balanced-match": "^1.0.0" + } + }, + "minimatch": { + "version": "9.0.3", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-9.0.3.tgz", + "integrity": "sha512-RHiac9mvaRw0x3AYRgDC1CxAP7HTcNrrECeA8YYJeWnpo+2Q5CegtZjaotWTWxDG3UeGA1coE05iH1mPjT/2mg==", + "dev": true, + "requires": { + "brace-expansion": "^2.0.1" + } + } + } + }, + "@vue/reactivity": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity/-/reactivity-3.3.4.tgz", + "integrity": "sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==", + "requires": { + "@vue/shared": "3.3.4" + } + }, + "@vue/reactivity-transform": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/reactivity-transform/-/reactivity-transform-3.3.4.tgz", + "integrity": "sha512-MXgwjako4nu5WFLAjpBnCj/ieqcjE2aJBINUNQzkZQfzIZA4xn+0fV1tIYBJvvva3N3OvKGofRLvQIwEQPpaXw==", + "requires": { + "@babel/parser": "^7.20.15", + "@vue/compiler-core": "3.3.4", + "@vue/shared": "3.3.4", + "estree-walker": "^2.0.2", + "magic-string": "^0.30.0" + } + }, + "@vue/runtime-core": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-core/-/runtime-core-3.3.4.tgz", + "integrity": "sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==", + "requires": { + "@vue/reactivity": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/runtime-dom": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/runtime-dom/-/runtime-dom-3.3.4.tgz", + "integrity": "sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==", + "requires": { + "@vue/runtime-core": "3.3.4", + "@vue/shared": "3.3.4", + "csstype": "^3.1.1" + } + }, + "@vue/server-renderer": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/server-renderer/-/server-renderer-3.3.4.tgz", + "integrity": "sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==", + "requires": { + "@vue/compiler-ssr": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "@vue/shared": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/@vue/shared/-/shared-3.3.4.tgz", + "integrity": "sha512-7OjdcV8vQ74eiz1TZLzZP4JwqM5fA94K6yntPS5Z25r9HDuGNzaGdgvwKYq6S+MxwF0TFRwe50fIR/MYnakdkQ==" + }, + "@vue/typescript": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/@vue/typescript/-/typescript-1.8.4.tgz", + "integrity": "sha512-sioQfIY5xcmEAz+cPLvv6CtzGPtGhIdR0Za87zB8M4mPe4OSsE3MBGkXcslf+EzQgF+fm6Gr1SRMSX8r5ZmzDA==", + "dev": true, + "requires": { + "@volar/typescript": "~1.8.0", + "@vue/language-core": "1.8.4" + } + }, + "@vuelidate/core": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/core/-/core-2.0.3.tgz", + "integrity": "sha512-AN6l7KF7+mEfyWG0doT96z+47ljwPpZfi9/JrNMkOGLFv27XVZvKzRLXlmDPQjPl/wOB1GNnHuc54jlCLRNqGA==", + "requires": { + "vue-demi": "^0.13.11" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, + "@vuelidate/validators": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/@vuelidate/validators/-/validators-2.0.3.tgz", + "integrity": "sha512-P4WtmczPRbwmVihKlSAs3pIjIil4xjHW+Qoi58MCw7vnKuYr/IHarOFkwp0K8eIIqiQqY4URM5Gb4aeDjna6Yw==", + "requires": { + "vue-demi": "^0.13.11" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + }, + "@vueuse/core": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/core/-/core-10.2.1.tgz", + "integrity": "sha512-c441bfMbkAwTNwVRHQ0zdYZNETK//P84rC01aP2Uy/aRFCiie9NE/k9KdIXbno0eDYP5NPUuWv0aA/I4Unr/7w==", + "requires": { + "@types/web-bluetooth": "^0.0.17", + "@vueuse/metadata": "10.2.1", + "@vueuse/shared": "10.2.1", + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + } + } + }, + "@vueuse/metadata": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/metadata/-/metadata-10.2.1.tgz", + "integrity": "sha512-3Gt68mY/i6bQvFqx7cuGBzrCCQu17OBaGWS5JdwISpMsHnMKKjC2FeB5OAfMcCQ0oINfADP3i9A4PPRo0peHdQ==" + }, + "@vueuse/router": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/router/-/router-10.2.1.tgz", + "integrity": "sha512-H/1T4fLzMmeBNEmcXlbqk6AEp0HQpzf+0eeNJ6fGrs3RWClE2i3nYEFbtxfQeSm/7nZ6nf/UhgahzUQdyMhIwQ==", + "requires": { + "@vueuse/shared": "10.2.1", + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + } + } + }, + "@vueuse/shared": { + "version": "10.2.1", + "resolved": "https://registry.npmjs.org/@vueuse/shared/-/shared-10.2.1.tgz", + "integrity": "sha512-QWHq2bSuGptkcxx4f4M/fBYC3Y8d3M2UYyLsyzoPgEoVzJURQ0oJeWXu79OiLlBb8gTKkqe4mO85T/sf39mmiw==", + "requires": { + "vue-demi": ">=0.14.5" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + } + } + }, + "acorn": { + "version": "8.10.0", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.10.0.tgz", + "integrity": "sha512-F0SAmZ8iUtS//m8DmCTA0jlh6TDKkHQyK6xc6V4KDTyZKA9dnvX9/3sRTVQrWm79glUAZbnmmNcdYwUIHWVybw==", + "dev": true + }, + "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, + "requires": {} + }, + "ajv": { + "version": "6.12.6", + "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz", + "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==", + "dev": true, + "requires": { + "fast-deep-equal": "^3.1.1", + "fast-json-stable-stringify": "^2.0.0", + "json-schema-traverse": "^0.4.1", + "uri-js": "^4.2.2" + } + }, + "ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==" + }, + "ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "requires": { + "color-convert": "^2.0.1" + } + }, + "any-promise": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/any-promise/-/any-promise-1.3.0.tgz", + "integrity": "sha512-7UvmKalWRt1wgjL1RrGxoSJW/0QZFIegpeGvZG9kjp8vrRu55XTHbwnqq2GpXm9uLbcuhxm3IqX9OB4MZR1b2A==", + "dev": true + }, + "anymatch": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz", + "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==", + "dev": true, + "requires": { + "normalize-path": "^3.0.0", + "picomatch": "^2.0.4" + } + }, + "arg": { + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/arg/-/arg-5.0.2.tgz", + "integrity": "sha512-PYjyFOLKQ9y57JvQ6QLo8dAgNqswh8M1RMJYdQduT6xbWSgK36P/Z/v+p888pM69jMMfS8Xd8F6I1kQ/I9HUGg==", + "dev": true + }, + "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 + }, + "array-buffer-byte-length": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/array-buffer-byte-length/-/array-buffer-byte-length-1.0.0.tgz", + "integrity": "sha512-LPuwb2P+NrQw3XhxGc36+XSvuBPopovXYTR9Ew++Du9Yb/bx5AzBfrIsBoj0EZUifjQU+sHL21sseZ3jerWO/A==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "is-array-buffer": "^3.0.1" + } + }, + "asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==" + }, + "autoprefixer": { + "version": "10.4.14", + "resolved": "https://registry.npmjs.org/autoprefixer/-/autoprefixer-10.4.14.tgz", + "integrity": "sha512-FQzyfOsTlwVzjHxKEqRIAdJx9niO6VCBCoEwax/VLSoQF29ggECcPuBqUMZ+u8jCZOPSy8b8/8KnuFbp0SaFZQ==", + "dev": true, + "requires": { + "browserslist": "^4.21.5", + "caniuse-lite": "^1.0.30001464", + "fraction.js": "^4.2.0", + "normalize-range": "^0.1.2", + "picocolors": "^1.0.0", + "postcss-value-parser": "^4.2.0" + } + }, + "available-typed-arrays": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/available-typed-arrays/-/available-typed-arrays-1.0.5.tgz", + "integrity": "sha512-DMD0KiN46eipeziST1LPP/STfDU0sufISXmjSgvVsoU2tqxctQeASejWcfNtxYKqETM1UxQ8sp2OrSBWpHY6sw==", + "dev": true + }, + "axios": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.4.0.tgz", + "integrity": "sha512-S4XCWMEmzvo64T9GfvQDOXgYRDJ/wsSZc7Jvdgx5u1sd0JwsuPLqb3SYmusag+edF6ziyMensPVqLTSc1PiSEA==", + "requires": { + "follow-redirects": "^1.15.0", + "form-data": "^4.0.0", + "proxy-from-env": "^1.1.0" + } + }, + "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 + }, + "base32-encode": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/base32-encode/-/base32-encode-2.0.0.tgz", + "integrity": "sha512-mlmkfc2WqdDtMl/id4qm3A7RjW6jxcbAoMjdRmsPiwQP0ufD4oXItYMnPgVHe80lnAIy+1xwzhHE1s4FoIceSw==", + "requires": { + "to-data-view": "^2.0.0" + } + }, + "binary-extensions": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz", + "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==", + "dev": true + }, + "boolbase": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/boolbase/-/boolbase-1.0.0.tgz", + "integrity": "sha512-JZOSA7Mo9sNGB8+UjSgzdLtokWAky1zbztM3WRLCbZ70/3cTANmQmOdR7y2g+J0e2WXywy1yS468tY+IruqEww==", + "dev": true + }, + "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, + "requires": { + "balanced-match": "^1.0.0", + "concat-map": "0.0.1" + } + }, + "braces": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz", + "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==", + "dev": true, + "requires": { + "fill-range": "^7.0.1" + } + }, + "browserslist": { + "version": "4.21.9", + "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.9.tgz", + "integrity": "sha512-M0MFoZzbUrRU4KNfCrDLnvyE7gub+peetoTid3TBIqtunaDJyXlwhakT+/VkvSXcfIzFfK/nkCs4nmyTmxdNSg==", + "dev": true, + "requires": { + "caniuse-lite": "^1.0.30001503", + "electron-to-chromium": "^1.4.431", + "node-releases": "^2.0.12", + "update-browserslist-db": "^1.0.11" + } + }, + "builtin-modules": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/builtin-modules/-/builtin-modules-1.1.1.tgz", + "integrity": "sha512-wxXCdllwGhI2kCC0MnvTGYTMvnVZTvqgypkiTI8Pa5tcz2i6VqsqwYGgqwXji+4RgCzms6EajE4IxiUH6HH8nQ==", + "dev": true + }, + "call-bind": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz", + "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "get-intrinsic": "^1.0.2" + } + }, + "callsites": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz", + "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==", + "dev": true + }, + "camel-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/camel-case/-/camel-case-4.1.2.tgz", + "integrity": "sha512-gxGWBrTT1JuMx6R+o5PTXMmUnhnVzLQ9SNutD4YqKtI6ap897t3tKECYla6gCWEkplXnlNybEkZg9GEGxKFCgw==", + "requires": { + "pascal-case": "^3.1.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "camelcase": { + "version": "5.3.1", + "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz", + "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg==", + "peer": true + }, + "camelcase-css": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/camelcase-css/-/camelcase-css-2.0.1.tgz", + "integrity": "sha512-QOSvevhslijgYwRx6Rv7zKdMF8lbRmx+uQGx2+vDc+KI/eBnsy9kit5aj23AgGu3pa4t9AgwbnXWqS+iOY+2aA==", + "dev": true + }, + "caniuse-lite": { + "version": "1.0.30001514", + "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001514.tgz", + "integrity": "sha512-ENcIpYBmwAAOm/V2cXgM7rZUrKKaqisZl4ZAI520FIkqGXUxJjmaIssbRW5HVVR5tyV6ygTLIm15aU8LUmQSaQ==", + "dev": true + }, + "capital-case": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/capital-case/-/capital-case-1.0.4.tgz", + "integrity": "sha512-ds37W8CytHgwnhGGTi88pcPyR15qoNkOpYwmMMfnWqqWgESapLqvDx6huFjQ5vqWSn2Z06173XNA7LtMOeUh1A==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "dev": true, + "requires": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + } + }, + "change-case": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/change-case/-/change-case-4.1.2.tgz", + "integrity": "sha512-bSxY2ws9OtviILG1EiY5K7NNxkqg/JnRnFxLtKQ96JaviiIxi7djMrSd0ECT9AC+lttClmYwKw53BWpOMblo7A==", + "requires": { + "camel-case": "^4.1.2", + "capital-case": "^1.0.4", + "constant-case": "^3.0.4", + "dot-case": "^3.0.4", + "header-case": "^2.0.4", + "no-case": "^3.0.4", + "param-case": "^3.0.4", + "pascal-case": "^3.1.2", + "path-case": "^3.0.4", + "sentence-case": "^3.0.4", + "snake-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "character-parser": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/character-parser/-/character-parser-2.2.0.tgz", + "integrity": "sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==", + "dev": true, + "requires": { + "is-regex": "^1.0.3" + } + }, + "chokidar": { + "version": "3.5.3", + "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz", + "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==", + "dev": true, + "requires": { + "anymatch": "~3.1.2", + "braces": "~3.0.2", + "fsevents": "~2.3.2", + "glob-parent": "~5.1.2", + "is-binary-path": "~2.1.0", + "is-glob": "~4.0.1", + "normalize-path": "~3.0.0", + "readdirp": "~3.6.0" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "cliui": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz", + "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==", + "peer": true, + "requires": { + "string-width": "^4.2.0", + "strip-ansi": "^6.0.0", + "wrap-ansi": "^6.2.0" + } + }, + "color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "requires": { + "color-name": "~1.1.4" + } + }, + "color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==" + }, + "combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "requires": { + "delayed-stream": "~1.0.0" + } + }, + "commander": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/commander/-/commander-4.1.1.tgz", + "integrity": "sha512-NOKm8xhkzAjzFx8B2v5OAHT+u5pRQc2UCa2Vq9jYL/31o2wi9mxBA7LIFs3sV5VSC49z6pEhfbMULvShKj26WA==", + "dev": true + }, + "concat-map": { + "version": "0.0.1", + "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz", + "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==", + "dev": true + }, + "constant-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/constant-case/-/constant-case-3.0.4.tgz", + "integrity": "sha512-I2hSBi7Vvs7BEuJDr5dDHfzb/Ruj3FyvFyh7KLilAjNQw3Be+xgqUBA2W6scVEcL0hL1dwPRtIqEPVUCKkSsyQ==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "cookie": { + "version": "0.4.2", + "resolved": "https://registry.npmjs.org/cookie/-/cookie-0.4.2.tgz", + "integrity": "sha512-aSWTXFzaKWkvHO1Ny/s+ePFpvKsPnjc551iI41v3ny/ow6tBG5Vd+FuqGNhh1LxOmVzOlGUriIlOaokOvhaStA==" + }, + "cross-spawn": { + "version": "7.0.3", + "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz", + "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==", + "dev": true, + "requires": { + "path-key": "^3.1.0", + "shebang-command": "^2.0.0", + "which": "^2.0.1" + } + }, + "cssesc": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/cssesc/-/cssesc-3.0.0.tgz", + "integrity": "sha512-/Tb/JcjK111nNScGob5MNtsntNM1aCNUDipB/TkwZFhyDrrE47SOx/18wF2bbjgc3ZzCSKW1T5nt5EbFoAz/Vg==", + "dev": true + }, + "csstype": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/csstype/-/csstype-3.1.2.tgz", + "integrity": "sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==" + }, + "de-indent": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/de-indent/-/de-indent-1.0.2.tgz", + "integrity": "sha512-e/1zu3xH5MQryN2zdVaF0OrdNLUbvWxzMbi+iNA6Bky7l1RoP8a2fIbRocyHclXt/arDrrR6lL3TqFD9pMQTsg==", + "dev": true + }, + "debug": { + "version": "4.3.4", + "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz", + "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==", + "dev": true, + "requires": { + "ms": "2.1.2" + } + }, + "decamelize": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz", + "integrity": "sha512-z2S+W9X73hAUUki+N+9Za2lBlun89zigOyGrsax+KUQ6wKW4ZoWpEYBkGhQjwAjjDCkWxhY0VKEhk8wzY7F5cA==", + "peer": true + }, + "deep-equal": { + "version": "2.2.1", + "resolved": "https://registry.npmjs.org/deep-equal/-/deep-equal-2.2.1.tgz", + "integrity": "sha512-lKdkdV6EOGoVn65XaOsPdH4rMxTZOnmFyuIkMjM1i5HHCbfjC97dawgTAy0deYNfuqUqW+Q5VrVaQYtUpSd6yQ==", + "dev": true, + "requires": { + "array-buffer-byte-length": "^1.0.0", + "call-bind": "^1.0.2", + "es-get-iterator": "^1.1.3", + "get-intrinsic": "^1.2.0", + "is-arguments": "^1.1.1", + "is-array-buffer": "^3.0.2", + "is-date-object": "^1.0.5", + "is-regex": "^1.1.4", + "is-shared-array-buffer": "^1.0.2", + "isarray": "^2.0.5", + "object-is": "^1.1.5", + "object-keys": "^1.1.1", + "object.assign": "^4.1.4", + "regexp.prototype.flags": "^1.5.0", + "side-channel": "^1.0.4", + "which-boxed-primitive": "^1.0.2", + "which-collection": "^1.0.1", + "which-typed-array": "^1.1.9" + } + }, + "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 + }, + "define-properties": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.2.0.tgz", + "integrity": "sha512-xvqAVKGfT1+UAvPwKTVw/njhdQ8ZhXK4lI0bCIuCMrp2up9nPnaDftrLtmpTazqd1o+UY4zgzU+avtMbDP+ldA==", + "dev": true, + "requires": { + "has-property-descriptors": "^1.0.0", + "object-keys": "^1.1.1" + } + }, + "delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==" + }, + "didyoumean": { + "version": "1.2.2", + "resolved": "https://registry.npmjs.org/didyoumean/-/didyoumean-1.2.2.tgz", + "integrity": "sha512-gxtyfqMg7GKyhQmb056K7M3xszy/myH8w+B4RT+QXBQsvAOdc3XymqDDPHx1BgPgsdAA5SIifona89YtRATDzw==", + "dev": true + }, + "diff": { + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/diff/-/diff-4.0.2.tgz", + "integrity": "sha512-58lmxKSA4BNyLz+HHMUzlOEpg09FV+ev6ZMe3vJihgdxzgcwZ8VoEEPmALCZG9LmqfVoNMMKpttIYTVG6uDY7A==", + "dev": true + }, + "dijkstrajs": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/dijkstrajs/-/dijkstrajs-1.0.3.tgz", + "integrity": "sha512-qiSlmBq9+BCdCA/L46dw8Uy93mloxsPSbwnm5yrKn2vMPiy8KyAskTF6zuV/j5BMsmOGZDPs7KjU+mjb670kfA==", + "peer": true + }, + "dlv": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/dlv/-/dlv-1.1.3.tgz", + "integrity": "sha512-+HlytyjlPKnIG8XuRG8WvmBP8xs8P71y+SKKS6ZXWoEgLuePxtDoUEiH7WkdePWrQ5JBpE6aoVqfZfJUQkjXwA==", + "dev": true + }, + "doctrine": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz", + "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==", + "dev": true, + "requires": { + "esutils": "^2.0.2" + } + }, + "dot-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/dot-case/-/dot-case-3.0.4.tgz", + "integrity": "sha512-Kv5nKlh6yRrdrGvxeJ2e5y2eRUpkUosIW4A2AS38zwSz27zu7ufDwQPi5Jhs3XAlGNetl3bmnGhQsMtkKJnj3w==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "dotenv": { + "version": "16.3.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.3.1.tgz", + "integrity": "sha512-IPzF4w4/Rd94bA9imS68tZBaYyBWSCE47V1RGuMrB94iyTOIEwRmVL2x/4An+6mETpLrKJ5hQkB8W4kFAadeIQ==", + "dev": true + }, + "electron-to-chromium": { + "version": "1.4.454", + "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.454.tgz", + "integrity": "sha512-pmf1rbAStw8UEQ0sr2cdJtWl48ZMuPD9Sto8HVQOq9vx9j2WgDEN6lYoaqFvqEHYOmGA9oRGn7LqWI9ta0YugQ==", + "dev": true + }, + "emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "peer": true + }, + "encode-utf8": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/encode-utf8/-/encode-utf8-1.0.3.tgz", + "integrity": "sha512-ucAnuBEhUK4boH2HjVYG5Q2mQyPorvv0u/ocS+zhdw0S8AlHYY+GOFhP1Gio5z4icpP2ivFSvhtFjQi8+T9ppw==", + "peer": true + }, + "es-get-iterator": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/es-get-iterator/-/es-get-iterator-1.1.3.tgz", + "integrity": "sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.3", + "has-symbols": "^1.0.3", + "is-arguments": "^1.1.1", + "is-map": "^2.0.2", + "is-set": "^2.0.2", + "is-string": "^1.0.7", + "isarray": "^2.0.5", + "stop-iteration-iterator": "^1.0.0" + } + }, + "esbuild": { + "version": "0.18.11", + "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.11.tgz", + "integrity": "sha512-i8u6mQF0JKJUlGR3OdFLKldJQMMs8OqM9Cc3UCi9XXziJ9WERM5bfkHaEAy0YAvPRMgqSW55W7xYn84XtEFTtA==", + "dev": true, + "requires": { + "@esbuild/android-arm": "0.18.11", + "@esbuild/android-arm64": "0.18.11", + "@esbuild/android-x64": "0.18.11", + "@esbuild/darwin-arm64": "0.18.11", + "@esbuild/darwin-x64": "0.18.11", + "@esbuild/freebsd-arm64": "0.18.11", + "@esbuild/freebsd-x64": "0.18.11", + "@esbuild/linux-arm": "0.18.11", + "@esbuild/linux-arm64": "0.18.11", + "@esbuild/linux-ia32": "0.18.11", + "@esbuild/linux-loong64": "0.18.11", + "@esbuild/linux-mips64el": "0.18.11", + "@esbuild/linux-ppc64": "0.18.11", + "@esbuild/linux-riscv64": "0.18.11", + "@esbuild/linux-s390x": "0.18.11", + "@esbuild/linux-x64": "0.18.11", + "@esbuild/netbsd-x64": "0.18.11", + "@esbuild/openbsd-x64": "0.18.11", + "@esbuild/sunos-x64": "0.18.11", + "@esbuild/win32-arm64": "0.18.11", + "@esbuild/win32-ia32": "0.18.11", + "@esbuild/win32-x64": "0.18.11" + } + }, + "escalade": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz", + "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==", + "dev": true + }, + "escape-string-regexp": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz", + "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==", + "dev": true + }, + "eslint": { + "version": "8.44.0", + "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.44.0.tgz", + "integrity": "sha512-0wpHoUbDUHgNCyvFB5aXLiQVfK9B0at6gUvzy83k4kAsQ/u769TQDX6iKC+aO4upIHO9WSaA3QoXYQDHbNwf1A==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.2.0", + "@eslint-community/regexpp": "^4.4.0", + "@eslint/eslintrc": "^2.1.0", + "@eslint/js": "8.44.0", + "@humanwhocodes/config-array": "^0.11.10", + "@humanwhocodes/module-importer": "^1.0.1", + "@nodelib/fs.walk": "^1.2.8", + "ajv": "^6.10.0", + "chalk": "^4.0.0", + "cross-spawn": "^7.0.2", + "debug": "^4.3.2", + "doctrine": "^3.0.0", + "escape-string-regexp": "^4.0.0", + "eslint-scope": "^7.2.0", + "eslint-visitor-keys": "^3.4.1", + "espree": "^9.6.0", + "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", + "import-fresh": "^3.0.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", + "strip-json-comments": "^3.1.0", + "text-table": "^0.2.0" + } + }, + "eslint-plugin-vue": { + "version": "9.15.1", + "resolved": "https://registry.npmjs.org/eslint-plugin-vue/-/eslint-plugin-vue-9.15.1.tgz", + "integrity": "sha512-CJE/oZOslvmAR9hf8SClTdQ9JLweghT6JCBQNrT2Iel1uVw0W0OLJxzvPd6CxmABKCvLrtyDnqGV37O7KQv6+A==", + "dev": true, + "requires": { + "@eslint-community/eslint-utils": "^4.3.0", + "natural-compare": "^1.4.0", + "nth-check": "^2.0.1", + "postcss-selector-parser": "^6.0.9", + "semver": "^7.3.5", + "vue-eslint-parser": "^9.3.0", + "xml-name-validator": "^4.0.0" + } + }, + "eslint-scope": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.2.0.tgz", + "integrity": "sha512-DYj5deGlHBfMt15J7rdtyKNq/Nqlv5KfU4iodrQ019XESsRnwXH9KAE0y3cwtUHDo2ob7CypAnCqefh6vioWRw==", + "dev": true, + "requires": { + "esrecurse": "^4.3.0", + "estraverse": "^5.2.0" + } + }, + "eslint-visitor-keys": { + "version": "3.4.1", + "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.4.1.tgz", + "integrity": "sha512-pZnmmLwYzf+kWaM/Qgrvpen51upAktaaiI01nsJD/Yr3lMOdNtq0cxkrrg16w64VtisN6okbs7Q8AfGqj4c9fA==", + "dev": true + }, + "espree": { + "version": "9.6.0", + "resolved": "https://registry.npmjs.org/espree/-/espree-9.6.0.tgz", + "integrity": "sha512-1FH/IiruXZ84tpUlm0aCUEwMl2Ho5ilqVh0VvQXw+byAz/4SAciyHLlfmL5WYqsvD38oymdUwBss0LtK8m4s/A==", + "dev": true, + "requires": { + "acorn": "^8.9.0", + "acorn-jsx": "^5.3.2", + "eslint-visitor-keys": "^3.4.1" + } + }, + "esprima": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz", + "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==", + "dev": true + }, + "esprima-extract-comments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/esprima-extract-comments/-/esprima-extract-comments-1.1.0.tgz", + "integrity": "sha512-sBQUnvJwpeE9QnPrxh7dpI/dp67erYG4WXEAreAMoelPRpMR7NWb4YtwRPn9b+H1uLQKl/qS8WYmyaljTpjIsw==", + "dev": true, + "requires": { + "esprima": "^4.0.0" + } + }, + "esquery": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.5.0.tgz", + "integrity": "sha512-YQLXUplAwJgCydQ78IMJywZCceoqk1oH01OERdSAJc/7U2AylwjhSCLDEtqwg811idIS/9fIU5GjG73IgjKMVg==", + "dev": true, + "requires": { + "estraverse": "^5.1.0" + } + }, + "esrecurse": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz", + "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==", + "dev": true, + "requires": { + "estraverse": "^5.2.0" + } + }, + "estraverse": { + "version": "5.3.0", + "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz", + "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==", + "dev": true + }, + "estree-walker": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/estree-walker/-/estree-walker-2.0.2.tgz", + "integrity": "sha512-Rfkk/Mp/DL7JVje3u18FxFujQlTNR2q6QfMSMB7AvCBx91NGj/ba3kCfza0f6dVDbw7YlRf/nDrn7pQrCCyQ/w==" + }, + "esutils": { + "version": "2.0.3", + "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz", + "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==", + "dev": true + }, + "extract-comments": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/extract-comments/-/extract-comments-1.1.0.tgz", + "integrity": "sha512-dzbZV2AdSSVW/4E7Ti5hZdHWbA+Z80RJsJhr5uiL10oyjl/gy7/o+HI1HwK4/WSZhlq4SNKU3oUzXlM13Qx02Q==", + "dev": true, + "requires": { + "esprima-extract-comments": "^1.1.0", + "parse-code-context": "^1.0.0" + } + }, + "fast-deep-equal": { + "version": "3.1.3", + "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz", + "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q==", + "dev": true + }, + "fast-glob": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/fast-glob/-/fast-glob-3.3.0.tgz", + "integrity": "sha512-ChDuvbOypPuNjO8yIDf36x7BlZX1smcUMTTcyoIjycexOxd6DFsKsg21qVBzEmr3G7fUKIRy2/psii+CIUt7FA==", + "dev": true, + "requires": { + "@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" + }, + "dependencies": { + "glob-parent": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz", + "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==", + "dev": true, + "requires": { + "is-glob": "^4.0.1" + } + } + } + }, + "fast-json-stable-stringify": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz", + "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw==", + "dev": true + }, + "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 + }, + "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, + "requires": { + "reusify": "^1.0.4" + } + }, + "file-entry-cache": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz", + "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==", + "dev": true, + "requires": { + "flat-cache": "^3.0.4" + } + }, + "fill-range": { + "version": "7.0.1", + "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz", + "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==", + "dev": true, + "requires": { + "to-regex-range": "^5.0.1" + } + }, + "find-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, + "requires": { + "locate-path": "^6.0.0", + "path-exists": "^4.0.0" + } + }, + "flat-cache": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz", + "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==", + "dev": true, + "requires": { + "flatted": "^3.1.0", + "rimraf": "^3.0.2" + } + }, + "flatted": { + "version": "3.2.7", + "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz", + "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ==", + "dev": true + }, + "follow-redirects": { + "version": "1.15.2", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.2.tgz", + "integrity": "sha512-VQLG33o04KaQ8uYi2tVNbdrWp1QWxNNea+nmIB4EVM28v0hmP17z7aG1+wAkNzVq4KeXTq3221ye5qTJP91JwA==" + }, + "for-each": { + "version": "0.3.3", + "resolved": "https://registry.npmjs.org/for-each/-/for-each-0.3.3.tgz", + "integrity": "sha512-jqYfLp7mo9vIyQf8ykW2v7A+2N4QjeCeI5+Dz9XraiO1ign81wjiH7Fb9vSOWvQfNtmSa4H2RoQTrrXivdUZmw==", + "dev": true, + "requires": { + "is-callable": "^1.1.3" + } + }, + "form-data": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.0.tgz", + "integrity": "sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==", + "requires": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "mime-types": "^2.1.12" + } + }, + "fraction.js": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/fraction.js/-/fraction.js-4.2.0.tgz", + "integrity": "sha512-MhLuK+2gUcnZe8ZHlaaINnQLl0xRIGRfcGk2yl8xoQAfHrSsL3rYu6FCmBdkdbhc9EPlwyGHewaRsvwRMJtAlA==", + "dev": true + }, + "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 + }, + "fsevents": { + "version": "2.3.2", + "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz", + "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==", + "dev": true, + "optional": true + }, + "function-bind": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz", + "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==", + "dev": true + }, + "functions-have-names": { + "version": "1.2.3", + "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz", + "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==", + "dev": true + }, + "get-caller-file": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz", + "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==", + "peer": true + }, + "get-intrinsic": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.2.1.tgz", + "integrity": "sha512-2DcsyfABl+gVHEfCOaTrWgyt+tb6MSEGmKq+kI5HwLbIYgjgmMcV8KQ41uaKz1xxUcn9tJtgFbQUEVcEbd0FYw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1", + "has": "^1.0.3", + "has-proto": "^1.0.1", + "has-symbols": "^1.0.3" + } + }, + "glob": { + "version": "7.2.3", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz", + "integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.1.1", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + }, + "glob-parent": { + "version": "6.0.2", + "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz", + "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==", + "dev": true, + "requires": { + "is-glob": "^4.0.3" + } + }, + "globals": { + "version": "13.20.0", + "resolved": "https://registry.npmjs.org/globals/-/globals-13.20.0.tgz", + "integrity": "sha512-Qg5QtVkCy/kv3FUSlu4ukeZDVf9ee0iXLAUYX13gbR17bnejFTzr4iS9bY7kwCf1NztRNm1t91fjOiyx4CSwPQ==", + "dev": true, + "requires": { + "type-fest": "^0.20.2" + } + }, + "gopd": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.0.1.tgz", + "integrity": "sha512-d65bNlIadxvpb/A2abVdlqKqV563juRnZ1Wtk6s1sIR8uNsXR70xqIzVqxVf1eTqDunwT2MkczEeaezCKTZhwA==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.3" + } + }, + "graphemer": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/graphemer/-/graphemer-1.4.0.tgz", + "integrity": "sha512-EtKwoO6kxCL9WO5xipiHTZlSzBm7WLT627TqC/uVRd0HKmq8NXyebnNYxDoBi7wt8eTWrUrKXCOVaFq9x1kgag==", + "dev": true + }, + "has": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz", + "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==", + "dev": true, + "requires": { + "function-bind": "^1.1.1" + } + }, + "has-bigints": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz", + "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==", + "dev": true + }, + "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 + }, + "has-property-descriptors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz", + "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.1.1" + } + }, + "has-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/has-proto/-/has-proto-1.0.1.tgz", + "integrity": "sha512-7qE+iP+O+bgF9clE5+UoBFzE65mlBiVj3tKCrlNQ0Ogwm0BjpT/gK4SlLYDMybDh5I3TCTKnPPa0oMG7JDYrhg==", + "dev": true + }, + "has-symbols": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz", + "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==", + "dev": true + }, + "has-tostringtag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz", + "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "he": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz", + "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==", + "dev": true + }, + "header-case": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/header-case/-/header-case-2.0.4.tgz", + "integrity": "sha512-H/vuk5TEEVZwrR0lp2zed9OCo1uAILMlx0JEMgC26rzyJJ3N1v6XkwHHXJQdR2doSjcGPM6OKPYoJgf0plJ11Q==", + "requires": { + "capital-case": "^1.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "ignore": { + "version": "5.2.4", + "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.4.tgz", + "integrity": "sha512-MAb38BcSbH0eHNBxn7ql2NH/kX33OkB3lZ1BNdh7ENeRChHTYsTvWrMubiIAMNS2llXEEgZ1MUOBtXChP3kaFQ==", + "dev": true + }, + "immutable": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/immutable/-/immutable-4.3.0.tgz", + "integrity": "sha512-0AOCmOip+xgJwEVTQj1EfiDDOkPmuyllDuTuEX+DDXUgapLAsBIfkg3sxCYyCEA8mQqZrrxPUGjcOQ2JS3WLkg==", + "dev": true + }, + "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, + "requires": { + "parent-module": "^1.0.0", + "resolve-from": "^4.0.0" + } + }, + "imurmurhash": { + "version": "0.1.4", + "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz", + "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==", + "dev": true + }, + "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, + "requires": { + "once": "^1.3.0", + "wrappy": "1" + } + }, + "inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "dev": true + }, + "internal-slot": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.5.tgz", + "integrity": "sha512-Y+R5hJrzs52QCG2laLn4udYVnxsfny9CpOhNhUvk/SSSVyF6T27FzRbF0sroPidSu3X8oEAkOn2K804mjpt6UQ==", + "dev": true, + "requires": { + "get-intrinsic": "^1.2.0", + "has": "^1.0.3", + "side-channel": "^1.0.4" + } + }, + "is-arguments": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/is-arguments/-/is-arguments-1.1.1.tgz", + "integrity": "sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-array-buffer": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/is-array-buffer/-/is-array-buffer-3.0.2.tgz", + "integrity": "sha512-y+FyyR/w8vfIRq4eQcM1EYgSTnmHXPqaF+IgzgraytCFq5Xh8lllDVmAZolPJiZttZLeFSINPYMaEJ7/vWUa1w==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.2.0", + "is-typed-array": "^1.1.10" + } + }, + "is-bigint": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz", + "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==", + "dev": true, + "requires": { + "has-bigints": "^1.0.1" + } + }, + "is-binary-path": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz", + "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==", + "dev": true, + "requires": { + "binary-extensions": "^2.0.0" + } + }, + "is-boolean-object": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz", + "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-callable": { + "version": "1.2.7", + "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz", + "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==", + "dev": true + }, + "is-core-module": { + "version": "2.12.1", + "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.12.1.tgz", + "integrity": "sha512-Q4ZuBAe2FUsKtyQJoQHlvP8OvBERxO3jEmy1I7hcRXcJBGGHFh/aJBswbXuS9sgrDH2QUO8ilkwNPHvHMd8clg==", + "dev": true, + "requires": { + "has": "^1.0.3" + } + }, + "is-date-object": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz", + "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-expression": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/is-expression/-/is-expression-4.0.0.tgz", + "integrity": "sha512-zMIXX63sxzG3XrkHkrAPvm/OVZVSCPNkwMHU8oTX7/U3AL78I0QXCEICXUM13BIa8TYGZ68PiTKfQz3yaTNr4A==", + "dev": true, + "requires": { + "acorn": "^7.1.1", + "object-assign": "^4.1.1" + }, + "dependencies": { + "acorn": { + "version": "7.4.1", + "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz", + "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==", + "dev": true + } + } + }, + "is-extglob": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz", + "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==", + "dev": true + }, + "is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "peer": true + }, + "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, + "requires": { + "is-extglob": "^2.1.1" + } + }, + "is-map": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-map/-/is-map-2.0.2.tgz", + "integrity": "sha512-cOZFQQozTha1f4MxLFzlgKYPTyj26picdZTx82hbc/Xf4K/tZOOXSCkMvU4pKioRXGDLJRn0GM7Upe7kR721yg==", + "dev": true + }, + "is-number": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz", + "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==", + "dev": true + }, + "is-number-object": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz", + "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "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 + }, + "is-regex": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz", + "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "has-tostringtag": "^1.0.0" + } + }, + "is-set": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-set/-/is-set-2.0.2.tgz", + "integrity": "sha512-+2cnTEZeY5z/iXGbLhPrOAaK/Mau5k5eXq9j14CpRTftq0pAJu2MwVRSZhyZWBzx3o6X795Lz6Bpb6R0GKf37g==", + "dev": true + }, + "is-shared-array-buffer": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz", + "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2" + } + }, + "is-string": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz", + "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==", + "dev": true, + "requires": { + "has-tostringtag": "^1.0.0" + } + }, + "is-symbol": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz", + "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==", + "dev": true, + "requires": { + "has-symbols": "^1.0.2" + } + }, + "is-typed-array": { + "version": "1.1.10", + "resolved": "https://registry.npmjs.org/is-typed-array/-/is-typed-array-1.1.10.tgz", + "integrity": "sha512-PJqgEHiWZvMpaFZ3uTc8kHPM4+4ADTlDniuQL7cU/UDA0Ql7F70yGfHph3cLNe+c9toaigv+DFzTJKhc2CtO6A==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0" + } + }, + "is-weakmap": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/is-weakmap/-/is-weakmap-2.0.1.tgz", + "integrity": "sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==", + "dev": true + }, + "is-weakset": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/is-weakset/-/is-weakset-2.0.2.tgz", + "integrity": "sha512-t2yVvttHkQktwnNNmBQ98AhENLdPUTDTE21uPqAQ0ARwQfGeQKRVS0NNurH7bTf7RrvcVn1OOge45CnBeHCSmg==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "get-intrinsic": "^1.1.1" + } + }, + "isarray": { + "version": "2.0.5", + "resolved": "https://registry.npmjs.org/isarray/-/isarray-2.0.5.tgz", + "integrity": "sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==", + "dev": true + }, + "isexe": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz", + "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw==", + "dev": true + }, + "jiti": { + "version": "1.19.1", + "resolved": "https://registry.npmjs.org/jiti/-/jiti-1.19.1.tgz", + "integrity": "sha512-oVhqoRDaBXf7sjkll95LHVS6Myyyb1zaunVwk4Z0+WPSW4gjS0pl01zYKHScTuyEhQsFxV5L4DR5r+YqSyqyyg==", + "dev": true + }, + "jose": { + "version": "4.14.4", + "resolved": "https://registry.npmjs.org/jose/-/jose-4.14.4.tgz", + "integrity": "sha512-j8GhLiKmUAh+dsFXlX1aJCbt5KMibuKb+d7j1JaOJG6s2UjX1PQlW+OKB/sD4a/5ZYF4RcmYmLSndOoU3Lt/3g==" + }, + "js-tokens": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz", + "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ==", + "dev": true + }, + "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, + "requires": { + "argparse": "^2.0.1" + } + }, + "json-editor-vue": { + "version": "0.10.6", + "resolved": "https://registry.npmjs.org/json-editor-vue/-/json-editor-vue-0.10.6.tgz", + "integrity": "sha512-jtKEmiCTLuri5uBkyb4qUrR6QwJgBJvBrYlFeY0L3NBE4RWDUlKy/swCcX1p32KmXKLjezzMSOvEEeAmQ6hjTw==", + "requires": { + "lodash-es": "latest", + "vue-demi": "latest", + "vue-global-config": "latest" + }, + "dependencies": { + "vue-demi": { + "version": "0.14.5", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.14.5.tgz", + "integrity": "sha512-o9NUVpl/YlsGJ7t+xuqJKx8EBGf1quRhCiT6D/J0pfwmk9zUwYkC7yrF4SZCe6fETvSM3UNL2edcbYrSyc4QHA==", + "requires": {} + }, + "vue-global-config": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/vue-global-config/-/vue-global-config-0.4.0.tgz", + "integrity": "sha512-UoFouTLUYGuHEEJFRgl29dzD0dDYtAEPRMDe8S0e08aB8vgfuD0ybW5WdPRTfTRChceHCn4eMQH2jFFVyi3Djw==", + "requires": { + "change-case": "^4.1.2", + "lodash-es": "^4.17.21", + "vue-demi": "^0.13.11" + }, + "dependencies": { + "vue-demi": { + "version": "0.13.11", + "resolved": "https://registry.npmjs.org/vue-demi/-/vue-demi-0.13.11.tgz", + "integrity": "sha512-IR8HoEEGM65YY3ZJYAjMlKygDQn25D5ajNFNoKh9RSDMQtlzCxtfQjdQgv9jjK+m3377SsJXY8ysq8kLCZL25A==", + "requires": {} + } + } + } + } + }, + "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 + }, + "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 + }, + "json5": { + "version": "2.2.3", + "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.3.tgz", + "integrity": "sha512-XmOWe7eyHYH14cLdVPoyg+GOH3rYX++KpzrylJwSW98t3Nk+U8XOl8FWKOgwtzdb8lXGf6zYwDUzeHMWfxasyg==", + "dev": true + }, + "jssha": { + "version": "3.3.0", + "resolved": "https://registry.npmjs.org/jssha/-/jssha-3.3.0.tgz", + "integrity": "sha512-w9OtT4ALL+fbbwG3gw7erAO0jvS5nfvrukGPMWIAoea359B26ALXGpzy4YJSp9yGnpUvuvOw1nSjSoHDfWSr1w==" + }, + "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, + "requires": { + "prelude-ls": "^1.2.1", + "type-check": "~0.4.0" + } + }, + "lilconfig": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/lilconfig/-/lilconfig-2.1.0.tgz", + "integrity": "sha512-utWOt/GHzuUxnLKxB6dk81RoOeoNeHgbrXiuGk4yyF5qlRz+iIVWu56E2fqGHFrXz0QNUhLB/8nKqvRH66JKGQ==", + "dev": true + }, + "lines-and-columns": { + "version": "1.2.4", + "resolved": "https://registry.npmjs.org/lines-and-columns/-/lines-and-columns-1.2.4.tgz", + "integrity": "sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==", + "dev": true + }, + "local-pkg": { + "version": "0.4.3", + "resolved": "https://registry.npmjs.org/local-pkg/-/local-pkg-0.4.3.tgz", + "integrity": "sha512-SFppqq5p42fe2qcZQqqEOiVRXl+WCP1MdT6k7BDEW1j++sp5fIY+/fdRQitvKgB5BrBcmrs5m/L0v2FrU5MY1g==", + "dev": true + }, + "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, + "requires": { + "p-locate": "^5.0.0" + } + }, + "lodash": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz", + "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg==" + }, + "lodash-es": { + "version": "4.17.21", + "resolved": "https://registry.npmjs.org/lodash-es/-/lodash-es-4.17.21.tgz", + "integrity": "sha512-mKnC+QJ9pWVzv+C4/U3rRsHapFfHvQFoFB92e52xeyGMcX6/OlIl78je1u8vePzYZSkkogMPJ2yjxxsb89cxyw==" + }, + "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 + }, + "lower-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/lower-case/-/lower-case-2.0.2.tgz", + "integrity": "sha512-7fm3l3NAF9WfN6W3JOmf5drwpVqX78JtoGJ3A6W0a6ZnldM41w2fV5D490psKFTpMds8TJse/eHLFFsNHHjHgg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "lru-cache": { + "version": "6.0.0", + "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz", + "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==", + "dev": true, + "requires": { + "yallist": "^4.0.0" + } + }, + "magic-string": { + "version": "0.30.1", + "resolved": "https://registry.npmjs.org/magic-string/-/magic-string-0.30.1.tgz", + "integrity": "sha512-mbVKXPmS0z0G4XqFDCTllmDQ6coZzn94aMlb0o/A4HEHJCKcanlDZwYJgwnkmgD3jyWhUgj9VsPrfd972yPffA==", + "requires": { + "@jridgewell/sourcemap-codec": "^1.4.15" + } + }, + "merge2": { + "version": "1.4.1", + "resolved": "https://registry.npmjs.org/merge2/-/merge2-1.4.1.tgz", + "integrity": "sha512-8q7VEgMJW4J8tcfVPy8g09NcQwZdbwFEqhe/WZkoIzjn/3TGDwtOCYtXGxA3O8tPzpczCCDgv+P2P5y00ZJOOg==", + "dev": true + }, + "micromatch": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/micromatch/-/micromatch-4.0.5.tgz", + "integrity": "sha512-DMy+ERcEW2q8Z2Po+WNXuw3c5YaUSFjAO5GsJqfEl7UjvtIuFKO6ZrKvcItdy98dwFI2N1tg3zNIdKaQT+aNdA==", + "dev": true, + "requires": { + "braces": "^3.0.2", + "picomatch": "^2.3.1" + } + }, + "mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==" + }, + "mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "requires": { + "mime-db": "1.52.0" + } + }, + "minimatch": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz", + "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==", + "dev": true, + "requires": { + "brace-expansion": "^1.1.7" + } + }, + "minimist": { + "version": "1.2.8", + "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.8.tgz", + "integrity": "sha512-2yyAR8qBkN3YuheJanUpWC5U3bb5osDywNB8RzDVlDwDHbocAJveqqj1u8+SVD7jkWT4yvsHCpWqqWqAxb0zCA==", + "dev": true + }, + "mkdirp": { + "version": "0.5.6", + "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.6.tgz", + "integrity": "sha512-FP+p8RB8OWpF3YZBCrP5gtADmtXApB5AMLn+vdyA+PyxCjrCs00mjyUozssO33cwDeT3wNGdLxJ5M//YqtHAJw==", + "dev": true, + "requires": { + "minimist": "^1.2.6" + } + }, + "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 + }, + "muggle-string": { + "version": "0.3.1", + "resolved": "https://registry.npmjs.org/muggle-string/-/muggle-string-0.3.1.tgz", + "integrity": "sha512-ckmWDJjphvd/FvZawgygcUeQCxzvohjFO5RxTjj4eq8kw359gFF3E1brjfI+viLMxss5JrHTDRHZvu2/tuy0Qg==", + "dev": true + }, + "mz": { + "version": "2.7.0", + "resolved": "https://registry.npmjs.org/mz/-/mz-2.7.0.tgz", + "integrity": "sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q==", + "dev": true, + "requires": { + "any-promise": "^1.0.0", + "object-assign": "^4.0.1", + "thenify-all": "^1.0.0" + } + }, + "nanoid": { + "version": "3.3.6", + "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", + "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==" + }, + "natural-compare": { + "version": "1.4.0", + "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz", + "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw==", + "dev": true + }, + "no-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/no-case/-/no-case-3.0.4.tgz", + "integrity": "sha512-fgAN3jGAh+RoxUGZHTSOLJIqUc2wmoBwGR4tbpNAKmmovFoWq0OdRkb0VkldReO2a2iBT/OEulG9XSUc10r3zg==", + "requires": { + "lower-case": "^2.0.2", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "node-releases": { + "version": "2.0.13", + "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.13.tgz", + "integrity": "sha512-uYr7J37ae/ORWdZeQ1xxMJe3NtdmqMC/JZK+geofDrkLUApKRHPd18/TxtBOJ4A0/+uUIliorNrfYV6s1b02eQ==", + "dev": true + }, + "normalize-path": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz", + "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==", + "dev": true + }, + "normalize-range": { + "version": "0.1.2", + "resolved": "https://registry.npmjs.org/normalize-range/-/normalize-range-0.1.2.tgz", + "integrity": "sha512-bdok/XvKII3nUpklnV6P2hxtMNrCboOjAcyBuQnWEhO665FwrSNRxU+AqpsyvO6LgGYPspN+lu5CLtw4jPRKNA==", + "dev": true + }, + "nth-check": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/nth-check/-/nth-check-2.1.1.tgz", + "integrity": "sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==", + "dev": true, + "requires": { + "boolbase": "^1.0.0" + } + }, + "object-assign": { + "version": "4.1.1", + "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz", + "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==", + "dev": true + }, + "object-hash": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/object-hash/-/object-hash-3.0.0.tgz", + "integrity": "sha512-RSn9F68PjH9HqtltsSnqYC1XXoWe9Bju5+213R98cNGttag9q9yAOTzdbsqvIa7aNm5WffBZFpWYr2aWrklWAw==", + "dev": true + }, + "object-inspect": { + "version": "1.12.3", + "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.3.tgz", + "integrity": "sha512-geUvdk7c+eizMNUDkRpW1wJwgfOiOeHbxBR/hLXK1aT6zmVSO0jsQcs7fj6MGw89jC/cjGfLcNOrtMYtGqm81g==", + "dev": true + }, + "object-is": { + "version": "1.1.5", + "resolved": "https://registry.npmjs.org/object-is/-/object-is-1.1.5.tgz", + "integrity": "sha512-3cyDsyHgtmi7I7DfSSI2LDp6SK2lwvtbg0p0R1e0RvTqF5ceGx+K2dfSjm1bKDMVCFEDAQvy+o8c6a7VujOddw==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.3" + } + }, + "object-keys": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz", + "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==", + "dev": true + }, + "object.assign": { + "version": "4.1.4", + "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz", + "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.1.4", + "has-symbols": "^1.0.3", + "object-keys": "^1.1.1" + } + }, + "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, + "requires": { + "wrappy": "1" + } + }, + "optionator": { + "version": "0.9.3", + "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.3.tgz", + "integrity": "sha512-JjCoypp+jKn1ttEFExxhetCKeJt9zhAgAve5FXHixTvFDW/5aEktX9bufBKLRRMdU7bNtpLfcGu94B3cdEJgjg==", + "dev": true, + "requires": { + "@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" + } + }, + "otpauth": { + "version": "9.1.3", + "resolved": "https://registry.npmjs.org/otpauth/-/otpauth-9.1.3.tgz", + "integrity": "sha512-lWy9GE2ASTgOwhH1dQsZwOIcVl2HopXNnTTtBXuhO4VZJ9AXT8Zx1ifTGaFUiXVFOzbHD/W4hDqMd/ZRDLkJEw==", + "requires": { + "jssha": "~3.3.0" + } + }, + "p-limit": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz", + "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==", + "dev": true, + "requires": { + "yocto-queue": "^0.1.0" + } + }, + "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, + "requires": { + "p-limit": "^3.0.2" + } + }, + "p-try": { + "version": "2.2.0", + "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz", + "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ==", + "peer": true + }, + "param-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/param-case/-/param-case-3.0.4.tgz", + "integrity": "sha512-RXlj7zCYokReqWpOPH9oYivUzLYZ5vAPIfEmCTNViosC78F8F0H9y7T7gG2M39ymgutxF5gcFEsyZQSph9Bp3A==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "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, + "requires": { + "callsites": "^3.0.0" + } + }, + "parse-code-context": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/parse-code-context/-/parse-code-context-1.0.0.tgz", + "integrity": "sha512-OZQaqKaQnR21iqhlnPfVisFjBWjhnMl5J9MgbP8xC+EwoVqbXrq78lp+9Zb3ahmLzrIX5Us/qbvBnaS3hkH6OA==", + "dev": true + }, + "pascal-case": { + "version": "3.1.2", + "resolved": "https://registry.npmjs.org/pascal-case/-/pascal-case-3.1.2.tgz", + "integrity": "sha512-uWlGT3YSnK9x3BQJaOdcZwrnV6hPpd8jFH1/ucpiLRPh/2zCVJKS19E4GvYHvaCcACn3foXZ0cLB9Wrx1KGe5g==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "path-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/path-case/-/path-case-3.0.4.tgz", + "integrity": "sha512-qO4qCFjXqVTrcbPt/hQfhTQ+VhFsqNKOPtytgNKkKxSoEp3XPUQ8ObFuePylOIok5gjn69ry8XiULxCwot3Wfg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "path-exists": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz", + "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==" + }, + "path-is-absolute": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz", + "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==", + "dev": true + }, + "path-key": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz", + "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==", + "dev": true + }, + "path-parse": { + "version": "1.0.7", + "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz", + "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==", + "dev": true + }, + "picocolors": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", + "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==" + }, + "picomatch": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz", + "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==", + "dev": true + }, + "pify": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/pify/-/pify-2.3.0.tgz", + "integrity": "sha512-udgsAY+fTnvv7kI7aaxbqwWNb0AHiB0qBO89PZKPkoTmGOgdbrHDKD+0B2X4uTfJ/FT1R09r9gTsjUjNJotuog==", + "dev": true + }, + "pirates": { + "version": "4.0.6", + "resolved": "https://registry.npmjs.org/pirates/-/pirates-4.0.6.tgz", + "integrity": "sha512-saLsH7WeYYPiD25LDuLRRY/i+6HaPYr6G1OUlN39otzkSTxKnubR9RTxS3/Kk50s1g2JTgFwWQDQyplC5/SHZg==", + "dev": true + }, + "pngjs": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/pngjs/-/pngjs-5.0.0.tgz", + "integrity": "sha512-40QW5YalBNfQo5yRYmiw7Yz6TKKVr3h6970B2YE+3fQpsWcrbj1PzJgxeJ19DRQjhMbKPIuMY8rFaXc8moolVw==", + "peer": true + }, + "postcss": { + "version": "8.4.25", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.25.tgz", + "integrity": "sha512-7taJ/8t2av0Z+sQEvNzCkpDynl0tX3uJMCODi6nT3PfASC7dYCWV9aQ+uiCf+KBD4SEFcu+GvJdGdwzQ6OSjCw==", + "requires": { + "nanoid": "^3.3.6", + "picocolors": "^1.0.0", + "source-map-js": "^1.0.2" + } + }, + "postcss-import": { + "version": "15.1.0", + "resolved": "https://registry.npmjs.org/postcss-import/-/postcss-import-15.1.0.tgz", + "integrity": "sha512-hpr+J05B2FVYUAXHeK1YyI267J/dDDhMU6B6civm8hSY1jYJnBXxzKDKDswzJmtLHryrjhnDjqqp/49t8FALew==", + "dev": true, + "requires": { + "postcss-value-parser": "^4.0.0", + "read-cache": "^1.0.0", + "resolve": "^1.1.7" + } + }, + "postcss-js": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-js/-/postcss-js-4.0.1.tgz", + "integrity": "sha512-dDLF8pEO191hJMtlHFPRa8xsizHaM82MLfNkUHdUtVEV3tgTp5oj+8qbEqYM57SLfc74KSbw//4SeJma2LRVIw==", + "dev": true, + "requires": { + "camelcase-css": "^2.0.1" + } + }, + "postcss-load-config": { + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/postcss-load-config/-/postcss-load-config-4.0.1.tgz", + "integrity": "sha512-vEJIc8RdiBRu3oRAI0ymerOn+7rPuMvRXslTvZUKZonDHFIczxztIyJ1urxM1x9JXEikvpWWTUUqal5j/8QgvA==", + "dev": true, + "requires": { + "lilconfig": "^2.0.5", + "yaml": "^2.1.1" + } + }, + "postcss-nested": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/postcss-nested/-/postcss-nested-6.0.1.tgz", + "integrity": "sha512-mEp4xPMi5bSWiMbsgoPfcP74lsWLHkQbZc3sY+jWYd65CUwXrUaTp0fmNpa01ZcETKlIgUdFN/MpS2xZtqL9dQ==", + "dev": true, + "requires": { + "postcss-selector-parser": "^6.0.11" + } + }, + "postcss-selector-parser": { + "version": "6.0.13", + "resolved": "https://registry.npmjs.org/postcss-selector-parser/-/postcss-selector-parser-6.0.13.tgz", + "integrity": "sha512-EaV1Gl4mUEV4ddhDnv/xtj7sxwrwxdetHdWUGnT4VJQf+4d05v6lHYZr8N573k5Z0BViss7BDhfWtKS3+sfAqQ==", + "dev": true, + "requires": { + "cssesc": "^3.0.0", + "util-deprecate": "^1.0.2" + } + }, + "postcss-value-parser": { + "version": "4.2.0", + "resolved": "https://registry.npmjs.org/postcss-value-parser/-/postcss-value-parser-4.2.0.tgz", + "integrity": "sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==", + "dev": true + }, + "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 + }, + "prettier": { + "version": "2.8.8", + "resolved": "https://registry.npmjs.org/prettier/-/prettier-2.8.8.tgz", + "integrity": "sha512-tdN8qQGvNjw4CHbY+XXk0JgCXn9QiF21a55rBe5LJAU+kDyC4WQn4+awm2Xfk2lQMk5fKup9XgzTZtGkjBdP9Q==", + "dev": true + }, + "proxy-from-env": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz", + "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg==" + }, + "pug-error": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/pug-error/-/pug-error-2.0.0.tgz", + "integrity": "sha512-sjiUsi9M4RAGHktC1drQfCr5C5eriu24Lfbt4s+7SykztEOwVZtbFk1RRq0tzLxcMxMYTBR+zMQaG07J/btayQ==", + "dev": true + }, + "pug-lexer": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/pug-lexer/-/pug-lexer-5.0.1.tgz", + "integrity": "sha512-0I6C62+keXlZPZkOJeVam9aBLVP2EnbeDw3An+k0/QlqdwH6rv8284nko14Na7c0TtqtogfWXcRoFE4O4Ff20w==", + "dev": true, + "requires": { + "character-parser": "^2.2.0", + "is-expression": "^4.0.0", + "pug-error": "^2.0.0" + } + }, + "punycode": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.3.0.tgz", + "integrity": "sha512-rRV+zQD8tVFys26lAGR9WUuS4iUAngJScM+ZRSKtvl5tKeZ2t5bvdNFdNHBW9FWR4guGHlgmsZ1G7BSm2wTbuA==", + "dev": true + }, + "qrcode": { + "version": "1.5.3", + "resolved": "https://registry.npmjs.org/qrcode/-/qrcode-1.5.3.tgz", + "integrity": "sha512-puyri6ApkEHYiVl4CFzo1tDkAZ+ATcnbJrJ6RiBM1Fhctdn/ix9MTE3hRph33omisEbC/2fcfemsseiKgBPKZg==", + "peer": true, + "requires": { + "dijkstrajs": "^1.0.1", + "encode-utf8": "^1.0.3", + "pngjs": "^5.0.0", + "yargs": "^15.3.1" + } + }, + "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 + }, + "read-cache": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/read-cache/-/read-cache-1.0.0.tgz", + "integrity": "sha512-Owdv/Ft7IjOgm/i0xvNDZ1LrRANRfew4b2prF3OWMQLxLfu3bS8FVhCsrSCMK4lR56Y9ya+AThoTpDCTxCmpRA==", + "dev": true, + "requires": { + "pify": "^2.3.0" + } + }, + "readdirp": { + "version": "3.6.0", + "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz", + "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==", + "dev": true, + "requires": { + "picomatch": "^2.2.1" + } + }, + "regexp.prototype.flags": { + "version": "1.5.0", + "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.5.0.tgz", + "integrity": "sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==", + "dev": true, + "requires": { + "call-bind": "^1.0.2", + "define-properties": "^1.2.0", + "functions-have-names": "^1.2.3" + } + }, + "require-directory": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz", + "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==", + "peer": true + }, + "require-main-filename": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz", + "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg==", + "peer": true + }, + "resolve": { + "version": "1.22.2", + "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.2.tgz", + "integrity": "sha512-Sb+mjNHOULsBv818T40qSPeRiuWLyaGMa5ewydRLFimneixmVy2zdivRl+AF6jaYPC8ERxGDmFSiqui6SfPd+g==", + "dev": true, + "requires": { + "is-core-module": "^2.11.0", + "path-parse": "^1.0.7", + "supports-preserve-symlinks-flag": "^1.0.0" + } + }, + "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 + }, + "reusify": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz", + "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==", + "dev": true + }, + "rimraf": { + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz", + "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==", + "dev": true, + "requires": { + "glob": "^7.1.3" + } + }, + "rollup": { + "version": "3.26.2", + "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.26.2.tgz", + "integrity": "sha512-6umBIGVz93er97pMgQO08LuH3m6PUb3jlDUUGFsNJB6VgTCUaDFpupf5JfU30529m/UKOgmiX+uY6Sx8cOYpLA==", + "dev": true, + "requires": { + "fsevents": "~2.3.2" + } + }, + "run-parallel": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz", + "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==", + "dev": true, + "requires": { + "queue-microtask": "^1.2.2" + } + }, + "sass": { + "version": "1.63.6", + "resolved": "https://registry.npmjs.org/sass/-/sass-1.63.6.tgz", + "integrity": "sha512-MJuxGMHzaOW7ipp+1KdELtqKbfAWbH7OLIdoSMnVe3EXPMTmxTmlaZDCTsgIpPCs3w99lLo9/zDKkOrJuT5byw==", + "dev": true, + "requires": { + "chokidar": ">=3.0.0 <4.0.0", + "immutable": "^4.0.0", + "source-map-js": ">=0.6.2 <2.0.0" + } + }, + "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, + "requires": { + "lru-cache": "^6.0.0" + } + }, + "sentence-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/sentence-case/-/sentence-case-3.0.4.tgz", + "integrity": "sha512-8LS0JInaQMCRoQ7YUytAo/xUu5W2XnQxV2HI/6uM6U7CITS1RqPElr30V6uIqyMKM9lJGRVFy5/4CuzcixNYSg==", + "requires": { + "no-case": "^3.0.4", + "tslib": "^2.0.3", + "upper-case-first": "^2.0.2" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "set-blocking": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz", + "integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==", + "peer": true + }, + "shebang-command": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz", + "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==", + "dev": true, + "requires": { + "shebang-regex": "^3.0.0" + } + }, + "shebang-regex": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz", + "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==", + "dev": true + }, + "showdown": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/showdown/-/showdown-2.1.0.tgz", + "integrity": "sha512-/6NVYu4U819R2pUIk79n67SYgJHWCce0a5xTP979WbNp0FL9MN1I1QK662IDU1b6JzKTvmhgI7T7JYIxBi3kMQ==", + "requires": { + "commander": "^9.0.0" + }, + "dependencies": { + "commander": { + "version": "9.5.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-9.5.0.tgz", + "integrity": "sha512-KRs7WVDKg86PWiuAqhDrAQnTXZKraVcCc6vFdL14qrZ/DcWwuRo7VoiYXalXO7S5GKpqYiVEwCbgFDfxNHKJBQ==" + } + } + }, + "side-channel": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz", + "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==", + "dev": true, + "requires": { + "call-bind": "^1.0.0", + "get-intrinsic": "^1.0.2", + "object-inspect": "^1.9.0" + } + }, + "snake-case": { + "version": "3.0.4", + "resolved": "https://registry.npmjs.org/snake-case/-/snake-case-3.0.4.tgz", + "integrity": "sha512-LAOh4z89bGQvl9pFfNF8V146i7o7/CqFPbqzYgP+yYzDIDeS9HaNFtXABamRW+AQzEVODcvE79ljJ+8a9YSdMg==", + "requires": { + "dot-case": "^3.0.4", + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "source-map-js": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", + "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==" + }, + "sprintf-js": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz", + "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==", + "dev": true + }, + "stop-iteration-iterator": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/stop-iteration-iterator/-/stop-iteration-iterator-1.0.0.tgz", + "integrity": "sha512-iCGQj+0l0HOdZ2AEeBADlsRC+vsnDsZsbdSiH1yNSjcfKM7fdpCMfqAL/dwF5BLiw/XhRft/Wax6zQbhq2BcjQ==", + "dev": true, + "requires": { + "internal-slot": "^1.0.4" + } + }, + "string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "peer": true, + "requires": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + } + }, + "strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "requires": { + "ansi-regex": "^5.0.1" + } + }, + "strip-json-comments": { + "version": "3.1.1", + "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz", + "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==", + "dev": true + }, + "sucrase": { + "version": "3.32.0", + "resolved": "https://registry.npmjs.org/sucrase/-/sucrase-3.32.0.tgz", + "integrity": "sha512-ydQOU34rpSyj2TGyz4D2p8rbktIOZ8QY9s+DGLvFU1i5pWJE8vkpruCjGCMHsdXwnD7JDcS+noSwM/a7zyNFDQ==", + "dev": true, + "requires": { + "@jridgewell/gen-mapping": "^0.3.2", + "commander": "^4.0.0", + "glob": "7.1.6", + "lines-and-columns": "^1.1.6", + "mz": "^2.7.0", + "pirates": "^4.0.1", + "ts-interface-checker": "^0.1.9" + }, + "dependencies": { + "glob": { + "version": "7.1.6", + "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz", + "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==", + "dev": true, + "requires": { + "fs.realpath": "^1.0.0", + "inflight": "^1.0.4", + "inherits": "2", + "minimatch": "^3.0.4", + "once": "^1.3.0", + "path-is-absolute": "^1.0.0" + } + } + } + }, + "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, + "requires": { + "has-flag": "^4.0.0" + } + }, + "supports-preserve-symlinks-flag": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz", + "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==", + "dev": true + }, + "tailwindcss": { + "version": "3.3.2", + "resolved": "https://registry.npmjs.org/tailwindcss/-/tailwindcss-3.3.2.tgz", + "integrity": "sha512-9jPkMiIBXvPc2KywkraqsUfbfj+dHDb+JPWtSJa9MLFdrPyazI7q6WX2sUrm7R9eVR7qqv3Pas7EvQFzxKnI6w==", + "dev": true, + "requires": { + "@alloc/quick-lru": "^5.2.0", + "arg": "^5.0.2", + "chokidar": "^3.5.3", + "didyoumean": "^1.2.2", + "dlv": "^1.1.3", + "fast-glob": "^3.2.12", + "glob-parent": "^6.0.2", + "is-glob": "^4.0.3", + "jiti": "^1.18.2", + "lilconfig": "^2.1.0", + "micromatch": "^4.0.5", + "normalize-path": "^3.0.0", + "object-hash": "^3.0.0", + "picocolors": "^1.0.0", + "postcss": "^8.4.23", + "postcss-import": "^15.1.0", + "postcss-js": "^4.0.1", + "postcss-load-config": "^4.0.1", + "postcss-nested": "^6.0.1", + "postcss-selector-parser": "^6.0.11", + "postcss-value-parser": "^4.2.0", + "resolve": "^1.22.2", + "sucrase": "^3.32.0" + } + }, + "text-table": { + "version": "0.2.0", + "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz", + "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw==", + "dev": true + }, + "thenify": { + "version": "3.3.1", + "resolved": "https://registry.npmjs.org/thenify/-/thenify-3.3.1.tgz", + "integrity": "sha512-RVZSIV5IG10Hk3enotrhvz0T9em6cyHBLkH/YAZuKqd8hRkKhSfCGIcP2KUY0EPxndzANBmNllzWPwak+bheSw==", + "dev": true, + "requires": { + "any-promise": "^1.0.0" + } + }, + "thenify-all": { + "version": "1.6.0", + "resolved": "https://registry.npmjs.org/thenify-all/-/thenify-all-1.6.0.tgz", + "integrity": "sha512-RNxQH/qI8/t3thXJDwcstUO4zeqo64+Uy/+sNVRBx4Xn2OX+OZ9oP+iJnNFqplFra2ZUVeKCSa2oVWi3T4uVmA==", + "dev": true, + "requires": { + "thenify": ">= 3.1.0 < 4" + } + }, + "to-data-view": { + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/to-data-view/-/to-data-view-2.0.0.tgz", + "integrity": "sha512-RGEM5KqlPHr+WVTPmGNAXNeFEmsBnlkxXaIfEpUYV0AST2Z5W1EGq9L/MENFrMMmL2WQr1wjkmZy/M92eKhjYA==" + }, + "to-regex-range": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz", + "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==", + "dev": true, + "requires": { + "is-number": "^7.0.0" + } + }, + "ts-interface-checker": { + "version": "0.1.13", + "resolved": "https://registry.npmjs.org/ts-interface-checker/-/ts-interface-checker-0.1.13.tgz", + "integrity": "sha512-Y/arvbn+rrz3JCKl9C4kVNfTfSm2/mEp5FSz5EsZSANGPSlQrpRI5M4PKF+mJnE52jOO90PnPSc3Ur3bTQw0gA==", + "dev": true + }, + "tslib": { + "version": "1.14.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz", + "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==", + "dev": true + }, + "tslint": { + "version": "6.1.3", + "resolved": "https://registry.npmjs.org/tslint/-/tslint-6.1.3.tgz", + "integrity": "sha512-IbR4nkT96EQOvKE2PW/djGz8iGNeJ4rF2mBfiYaR/nvUWYKJhLwimoJKgjIFEIDibBtOevj7BqCRL4oHeWWUCg==", + "dev": true, + "requires": { + "@babel/code-frame": "^7.0.0", + "builtin-modules": "^1.1.1", + "chalk": "^2.3.0", + "commander": "^2.12.1", + "diff": "^4.0.1", + "glob": "^7.1.1", + "js-yaml": "^3.13.1", + "minimatch": "^3.0.4", + "mkdirp": "^0.5.3", + "resolve": "^1.3.2", + "semver": "^5.3.0", + "tslib": "^1.13.0", + "tsutils": "^2.29.0" + }, + "dependencies": { + "ansi-styles": { + "version": "3.2.1", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz", + "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==", + "dev": true, + "requires": { + "color-convert": "^1.9.0" + } + }, + "argparse": { + "version": "1.0.10", + "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz", + "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==", + "dev": true, + "requires": { + "sprintf-js": "~1.0.2" + } + }, + "chalk": { + "version": "2.4.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz", + "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==", + "dev": true, + "requires": { + "ansi-styles": "^3.2.1", + "escape-string-regexp": "^1.0.5", + "supports-color": "^5.3.0" + } + }, + "color-convert": { + "version": "1.9.3", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz", + "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==", + "dev": true, + "requires": { + "color-name": "1.1.3" + } + }, + "color-name": { + "version": "1.1.3", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz", + "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw==", + "dev": true + }, + "commander": { + "version": "2.20.3", + "resolved": "https://registry.npmjs.org/commander/-/commander-2.20.3.tgz", + "integrity": "sha512-GpVkmM8vF2vQUkj2LvZmD35JxeJOLCwJ9cUkugyk2nuhbv3+mJvpLYYt+0+USMxE+oj+ey/lJEnhZw75x/OMcQ==", + "dev": true + }, + "escape-string-regexp": { + "version": "1.0.5", + "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz", + "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==", + "dev": true + }, + "has-flag": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz", + "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==", + "dev": true + }, + "js-yaml": { + "version": "3.14.1", + "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz", + "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==", + "dev": true, + "requires": { + "argparse": "^1.0.7", + "esprima": "^4.0.0" + } + }, + "semver": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/semver/-/semver-5.7.1.tgz", + "integrity": "sha512-sauaDf/PZdVgrLTNYHRtpXa1iRiKcaebiKQ1BJdpQlWH2lCvexQdX55snPFyK7QzpudqbCI0qXFfOasHdyNDGQ==", + "dev": true + }, + "supports-color": { + "version": "5.5.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz", + "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==", + "dev": true, + "requires": { + "has-flag": "^3.0.0" + } + } + } + }, + "tsutils": { + "version": "2.29.0", + "resolved": "https://registry.npmjs.org/tsutils/-/tsutils-2.29.0.tgz", + "integrity": "sha512-g5JVHCIJwzfISaXpXE1qvNalca5Jwob6FjI4AoPlqMusJ6ftFE7IkkFoMhVLRgK+4Kx3gkzb8UZK5t5yTTvEmA==", + "dev": true, + "requires": { + "tslib": "^1.8.1" + } + }, + "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, + "requires": { + "prelude-ls": "^1.2.1" + } + }, + "type-fest": { + "version": "0.20.2", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz", + "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==", + "dev": true + }, + "typescript": { + "version": "5.1.6", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-5.1.6.tgz", + "integrity": "sha512-zaWCozRZ6DLEWAWFrVDz1H6FVXzUSfTy5FUMWsQlU8Ym5JP9eO4xkTIROFCQvhQf61z6O/G6ugw3SgAnvvm+HA==", + "dev": true + }, + "universal-cookie": { + "version": "4.0.4", + "resolved": "https://registry.npmjs.org/universal-cookie/-/universal-cookie-4.0.4.tgz", + "integrity": "sha512-lbRVHoOMtItjWbM7TwDLdl8wug7izB0tq3/YVKhT/ahB4VDvWMyvnADfnJI8y6fSvsjh51Ix7lTGC6Tn4rMPhw==", + "requires": { + "@types/cookie": "^0.3.3", + "cookie": "^0.4.0" + } + }, + "update-browserslist-db": { + "version": "1.0.11", + "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.11.tgz", + "integrity": "sha512-dCwEFf0/oT85M1fHBg4F0jtLwJrutGoHSQXCh7u4o2t1drG+c0a9Flnqww6XUKSfQMPpJBRjU8d4RXB09qtvaA==", + "dev": true, + "requires": { + "escalade": "^3.1.1", + "picocolors": "^1.0.0" + } + }, + "upper-case": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case/-/upper-case-2.0.2.tgz", + "integrity": "sha512-KgdgDGJt2TpuwBUIjgG6lzw2GWFRCW9Qkfkiv0DxqHHLYJHmtmdUIKcZd8rHgFSjopVTlw6ggzCm1b8MFQwikg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "upper-case-first": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/upper-case-first/-/upper-case-first-2.0.2.tgz", + "integrity": "sha512-514ppYHBaKwfJRK/pNC6c/OxfGa0obSnAl106u97Ed0I625Nin96KAjttZF6ZL3e1XLtphxnqrOi9iWgm+u+bg==", + "requires": { + "tslib": "^2.0.3" + }, + "dependencies": { + "tslib": { + "version": "2.6.0", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.6.0.tgz", + "integrity": "sha512-7At1WUettjcSRHXCyYtTselblcHl9PJFFVKiCAy/bY97+BPZXSQ2wbq0P9s8tK2G7dFQfNnlJnPAiArVBVBsfA==" + } + } + }, + "uri-js": { + "version": "4.4.1", + "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz", + "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==", + "dev": true, + "requires": { + "punycode": "^2.1.0" + } + }, + "util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "dev": true + }, + "vanilla-jsoneditor": { + "version": "0.17.8", + "resolved": "https://registry.npmjs.org/vanilla-jsoneditor/-/vanilla-jsoneditor-0.17.8.tgz", + "integrity": "sha512-DP9GP/IBQjYOnC820CYoFuXs3vgrL+zdGGp1X83qFirSkRWPeP+6zB/14a0LYhvomQNNezes5Gwel89MKc4Qbg==" + }, + "vite": { + "version": "4.4.2", + "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.2.tgz", + "integrity": "sha512-zUcsJN+UvdSyHhYa277UHhiJ3iq4hUBwHavOpsNUGsTgjBeoBlK8eDt+iT09pBq0h9/knhG/SPrZiM7cGmg7NA==", + "dev": true, + "requires": { + "esbuild": "^0.18.10", + "fsevents": "~2.3.2", + "postcss": "^8.4.24", + "rollup": "^3.25.2" + } + }, + "vite-plugin-pages": { + "version": "0.31.0", + "resolved": "https://registry.npmjs.org/vite-plugin-pages/-/vite-plugin-pages-0.31.0.tgz", + "integrity": "sha512-fw3onBfVTXQI7rOzAbSZhmfwvk50+3qNnGZpERjmD93c8nEjrGLyd53eFXYMxcJV4KA1vzi4qIHt2+6tS4dEMw==", + "dev": true, + "requires": { + "@types/debug": "^4.1.8", + "debug": "^4.3.4", + "deep-equal": "^2.2.1", + "extract-comments": "^1.1.0", + "fast-glob": "^3.2.12", + "json5": "^2.2.3", + "local-pkg": "^0.4.3", + "picocolors": "^1.0.0", + "yaml": "^2.3.1" + } + }, + "vls": { + "version": "0.8.5", + "resolved": "https://registry.npmjs.org/vls/-/vls-0.8.5.tgz", + "integrity": "sha512-61kbdO2COZWBMC4wq59QfDdev9ruXd0226f57DFJTFpFXv85S+qnHakQlAmbSYFFLGKcx95HB2UjnuQh4YRwFA==", + "dev": true, + "requires": { + "eslint": "^8.34.0", + "eslint-plugin-vue": "^9.9.0", + "prettier": "^2.8.4", + "pug-lexer": "^5.0.1", + "tslint": "6.1.3", + "typescript": "^4.9.5" + }, + "dependencies": { + "typescript": { + "version": "4.9.5", + "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.9.5.tgz", + "integrity": "sha512-1FXk9E2Hm+QzZQ7z+McJiHL4NW1F2EzMu9Nq9i3zAaGqibafqYwCVU6WyWAuyQRRzOlxou8xZSyXLEN8oKj24g==", + "dev": true + } + } + }, + "vscode-html-languageservice": { + "version": "5.0.6", + "resolved": "https://registry.npmjs.org/vscode-html-languageservice/-/vscode-html-languageservice-5.0.6.tgz", + "integrity": "sha512-gCixNg6fjPO7+kwSMBAVXcwDRHdjz1WOyNfI0n5Wx0J7dfHG8ggb3zD1FI8E2daTZrwS1cooOiSoc1Xxph4qRQ==", + "dev": true, + "requires": { + "@vscode/l10n": "^0.0.14", + "vscode-languageserver-textdocument": "^1.0.8", + "vscode-languageserver-types": "^3.17.3", + "vscode-uri": "^3.0.7" + } + }, + "vscode-languageserver-textdocument": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/vscode-languageserver-textdocument/-/vscode-languageserver-textdocument-1.0.8.tgz", + "integrity": "sha512-1bonkGqQs5/fxGT5UchTgjGVnfysL0O8v1AYMBjqTbWQTFn721zaPGDYFkOKtfDgFiSgXM3KwaG3FMGfW4Ed9Q==", + "dev": true + }, + "vscode-languageserver-types": { + "version": "3.17.3", + "resolved": "https://registry.npmjs.org/vscode-languageserver-types/-/vscode-languageserver-types-3.17.3.tgz", + "integrity": "sha512-SYU4z1dL0PyIMd4Vj8YOqFvHu7Hz/enbWtpfnVbJHU4Nd1YNYx8u0ennumc6h48GQNeOLxmwySmnADouT/AuZA==", + "dev": true + }, + "vscode-uri": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/vscode-uri/-/vscode-uri-3.0.7.tgz", + "integrity": "sha512-eOpPHogvorZRobNqJGhapa0JdwaxpjVvyBp0QIUMRMSf8ZAlqOdEquKuRmw9Qwu0qXtJIWqFtMkmvJjUZmMjVA==", + "dev": true + }, + "vue": { + "version": "3.3.4", + "resolved": "https://registry.npmjs.org/vue/-/vue-3.3.4.tgz", + "integrity": "sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==", + "requires": { + "@vue/compiler-dom": "3.3.4", + "@vue/compiler-sfc": "3.3.4", + "@vue/runtime-dom": "3.3.4", + "@vue/server-renderer": "3.3.4", + "@vue/shared": "3.3.4" + } + }, + "vue-eslint-parser": { + "version": "9.3.1", + "resolved": "https://registry.npmjs.org/vue-eslint-parser/-/vue-eslint-parser-9.3.1.tgz", + "integrity": "sha512-Clr85iD2XFZ3lJ52/ppmUDG/spxQu6+MAeHXjjyI4I1NUYZ9xmenQp4N0oaHJhrA8OOxltCVxMRfANGa70vU0g==", + "dev": true, + "requires": { + "debug": "^4.3.4", + "eslint-scope": "^7.1.1", + "eslint-visitor-keys": "^3.3.0", + "espree": "^9.3.1", + "esquery": "^1.4.0", + "lodash": "^4.17.21", + "semver": "^7.3.6" + } + }, + "vue-router": { + "version": "4.2.4", + "resolved": "https://registry.npmjs.org/vue-router/-/vue-router-4.2.4.tgz", + "integrity": "sha512-9PISkmaCO02OzPVOMq2w82ilty6+xJmQrarYZDkjZBfl4RvYAlt4PKnEX21oW4KTtWfa9OuO/b3qk1Od3AEdCQ==", + "requires": { + "@vue/devtools-api": "^6.5.0" + } + }, + "vue-template-compiler": { + "version": "2.7.14", + "resolved": "https://registry.npmjs.org/vue-template-compiler/-/vue-template-compiler-2.7.14.tgz", + "integrity": "sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==", + "dev": true, + "requires": { + "de-indent": "^1.0.2", + "he": "^1.2.0" + } + }, + "vue-tsc": { + "version": "1.8.4", + "resolved": "https://registry.npmjs.org/vue-tsc/-/vue-tsc-1.8.4.tgz", + "integrity": "sha512-+hgpOhIx11vbi8/AxEdaPj3fiRwN9wy78LpsNNw2V995/IWa6TMyQxHbaw2ZKUpdwjySSHgrT6ohDEhUgFxGYw==", + "dev": true, + "requires": { + "@vue/language-core": "1.8.4", + "@vue/typescript": "1.8.4", + "semver": "^7.3.8" + } + }, + "vue3-otp-input": { + "version": "0.4.1", + "resolved": "https://registry.npmjs.org/vue3-otp-input/-/vue3-otp-input-0.4.1.tgz", + "integrity": "sha512-wVl9i3DcWlO0C7fBI9V+RIP3crm/1tY72fuhvb3YM2JfbLoYofB96aPl5AgFhA0Cse5bQEMYtIvOeiqW3rfbAw==", + "requires": {} + }, + "which": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz", + "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==", + "dev": true, + "requires": { + "isexe": "^2.0.0" + } + }, + "which-boxed-primitive": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz", + "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==", + "dev": true, + "requires": { + "is-bigint": "^1.0.1", + "is-boolean-object": "^1.1.0", + "is-number-object": "^1.0.4", + "is-string": "^1.0.5", + "is-symbol": "^1.0.3" + } + }, + "which-collection": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/which-collection/-/which-collection-1.0.1.tgz", + "integrity": "sha512-W8xeTUwaln8i3K/cY1nGXzdnVZlidBcagyNFtBdD5kxnb4TvGKR7FfSIS3mYpwWS1QUCutfKz8IY8RjftB0+1A==", + "dev": true, + "requires": { + "is-map": "^2.0.1", + "is-set": "^2.0.1", + "is-weakmap": "^2.0.1", + "is-weakset": "^2.0.1" + } + }, + "which-module": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.1.tgz", + "integrity": "sha512-iBdZ57RDvnOR9AGBhML2vFZf7h8vmBjhoaZqODJBFWHVtKkDmKuHai3cx5PgVMrX5YDNp27AofYbAwctSS+vhQ==", + "peer": true + }, + "which-typed-array": { + "version": "1.1.9", + "resolved": "https://registry.npmjs.org/which-typed-array/-/which-typed-array-1.1.9.tgz", + "integrity": "sha512-w9c4xkx6mPidwp7180ckYWfMmvxpjlZuIudNtDf4N/tTAUB8VJbX25qZoAsrtGuYNnGw3pa0AXgbGKRB8/EceA==", + "dev": true, + "requires": { + "available-typed-arrays": "^1.0.5", + "call-bind": "^1.0.2", + "for-each": "^0.3.3", + "gopd": "^1.0.1", + "has-tostringtag": "^1.0.0", + "is-typed-array": "^1.1.10" + } + }, + "wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "peer": true, + "requires": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + } + }, + "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 + }, + "xml-name-validator": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/xml-name-validator/-/xml-name-validator-4.0.0.tgz", + "integrity": "sha512-ICP2e+jsHvAj2E2lIHxa5tjXRlKDJo4IdvPvCXbXQGdzSfmSpNVyIKMvoZHjDY9DP0zV17iI85o90vRFXNccRw==", + "dev": true + }, + "y18n": { + "version": "4.0.3", + "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.3.tgz", + "integrity": "sha512-JKhqTOwSrqNA1NY5lSztJ1GrBiUodLMmIZuLiDaMRJ+itFd+ABVE8XBjOvIWL+rSqNDC74LCSFmlb/U4UZ4hJQ==", + "peer": true + }, + "yallist": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz", + "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==", + "dev": true + }, + "yaml": { + "version": "2.3.1", + "resolved": "https://registry.npmjs.org/yaml/-/yaml-2.3.1.tgz", + "integrity": "sha512-2eHWfjaoXgTBC2jNM1LRef62VQa0umtvRiDSk6HSzW7RvS5YtkabJrwYLLEKWBc8a5U2PTSCs+dJjUTJdlHsWQ==", + "dev": true + }, + "yargs": { + "version": "15.4.1", + "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz", + "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==", + "peer": true, + "requires": { + "cliui": "^6.0.0", + "decamelize": "^1.2.0", + "find-up": "^4.1.0", + "get-caller-file": "^2.0.1", + "require-directory": "^2.1.1", + "require-main-filename": "^2.0.0", + "set-blocking": "^2.0.0", + "string-width": "^4.2.0", + "which-module": "^2.0.0", + "y18n": "^4.0.0", + "yargs-parser": "^18.1.2" + }, + "dependencies": { + "find-up": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz", + "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==", + "peer": true, + "requires": { + "locate-path": "^5.0.0", + "path-exists": "^4.0.0" + } + }, + "locate-path": { + "version": "5.0.0", + "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz", + "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==", + "peer": true, + "requires": { + "p-locate": "^4.1.0" + } + }, + "p-limit": { + "version": "2.3.0", + "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz", + "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==", + "peer": true, + "requires": { + "p-try": "^2.0.0" + } + }, + "p-locate": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz", + "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==", + "peer": true, + "requires": { + "p-limit": "^2.2.0" + } + } + } + }, + "yargs-parser": { + "version": "18.1.3", + "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz", + "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==", + "peer": true, + "requires": { + "camelcase": "^5.0.0", + "decamelize": "^1.2.0" + } + }, + "yocto-queue": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz", + "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==", + "dev": true + } + } +} diff --git a/front-end/package.json b/front-end/package.json new file mode 100644 index 0000000..a043b4c --- /dev/null +++ b/front-end/package.json @@ -0,0 +1,58 @@ +{ + "name": "@vnuge/cmnext-front-end", + "private": true, + "version": "0.1.0", + "type": "module", + "main": "index.html", + "description": "The CMNext admin web UI, built with Tailwindcss and Vuejs", + "scripts": { + "dev": "vite", + "build": "vite build", + "preview": "vite preview" + }, + "dependencies": { + "@chenfengyuan/vue-qrcode": "^2.0.0", + "@fontsource/nunito": "^5.0.3", + "@fortawesome/fontawesome-svg-core": "^6.4.0", + "@fortawesome/free-brands-svg-icons": "^6.4.0", + "@fortawesome/free-solid-svg-icons": "^6.4.0", + "@fortawesome/vue-fontawesome": "^3.0.3", + "@headlessui/vue": "^1.7.12", + "@kyvg/vue3-notification": "^2.9.0", + "@vnuge/cmnext-admin": "../lib/admin", + "@vnuge/vnlib.browser": "https://www.vaughnnugent.com/public/resources/software/releases/vnlib-browser/v0.1.5.tgz", + "@vuelidate/core": "^2.0.2", + "@vuelidate/validators": "^2.0.2", + "@vueuse/core": "^10.1.2", + "@vueuse/router": "^10.1.2", + "axios": "^1.4.0", + "base32-encode": "^2.0.0", + "jose": "^4.14.4", + "json-editor-vue": "^0.10.6", + "lodash": "^4.17.21", + "otpauth": "^9.1.2", + "showdown": "^2.1.0", + "universal-cookie": "^4.0.4", + "vanilla-jsoneditor": "^0.17.8", + "vue": "^3.2.47", + "vue3-otp-input": "^0.4.1" + }, + + "devDependencies": { + "@types/showdown": "^2.0.1", + "@types/lodash": "^4.14.194", + "@vitejs/plugin-vue": "^4.1.0", + "@volar-plugins/vetur": "latest", + "autoprefixer": "^10.4.14", + "dotenv": "^16.0.3", + "postcss": "^8.4.23", + "sass": "^1.62.1", + "tailwindcss": "^3.3.2", + "typescript": "^5.0.2", + "vite": "^4.3.5", + "vite-plugin-pages": "^0.31.0", + "vue-eslint-parser": "^9.3.0", + "vue-router": "^4.2.0", + "vue-tsc": "^1.4.2" + } +} diff --git a/front-end/postcss.config.js b/front-end/postcss.config.js new file mode 100644 index 0000000..7970ff9 --- /dev/null +++ b/front-end/postcss.config.js @@ -0,0 +1,10 @@ +import tailwind from 'tailwindcss' +import autoprefixer from 'autoprefixer' +import tailwindConfig from './tailwind.config.js' + +export default { + plugins: [ + tailwind(tailwindConfig), + autoprefixer + ], +}
\ No newline at end of file diff --git a/front-end/src/App.vue b/front-end/src/App.vue new file mode 100644 index 0000000..92b8d5c --- /dev/null +++ b/front-end/src/App.vue @@ -0,0 +1,14 @@ +<template> + <!-- Import environment component top level as the entrypoint --> + <Environment> + <template #main> + <router-view /> + </template> + </Environment> + +</template> + +<script setup lang="ts"> +import Environment from './bootstrap/Environment.vue'; + +</script>
\ No newline at end of file diff --git a/front-end/src/assets/main.scss b/front-end/src/assets/main.scss new file mode 100644 index 0000000..e69de29 --- /dev/null +++ b/front-end/src/assets/main.scss diff --git a/front-end/src/bootstrap/Environment.vue b/front-end/src/bootstrap/Environment.vue new file mode 100644 index 0000000..c077869 --- /dev/null +++ b/front-end/src/bootstrap/Environment.vue @@ -0,0 +1,106 @@ +<template> + <head> + <title>{{ metaTile }}</title> + </head> + <div id="env-entry" ref="content" class="absolute top-0 left-0 w-full min-h-screen env-bg"> + <div class="absolute flex w-full"> + <notifications + class="general-toast" + group="general" + position="top" + :style="generalToastStyle" + /> + </div> + <div class="absolute flex w-full"> + <notifications + class="form-toast" + group="form" + position="top" + :style="formToastStyle" + /> + </div> + + <site-header ref="header" :site-title="siteTitle" :routes="routes" > + <template #site_logo> + <!-- Use the global site-logo if enabled --> + <site-logo /> + </template> + </site-header> + + <div id="env-body" class="flex w-full" :style="bodyStyle"> + <cookie-warning :hidden="showCookieWarning" /> + + <slot name="main" /> + </div> + + <!-- Setup footer with nav elements from global config --> + <site-footer ref="footer"> + <template #footer-nav-1> + <footer-nav-1/> + </template> + <template #footer-nav-2> + <footer-nav-2/> + </template> + </site-footer> + + <PasswordPrompt /> + <ConfirmPrompt /> + </div> +</template> + +<script setup lang="ts"> + +import { computed } from 'vue' +import { RouteLocation, useRouter } from 'vue-router' +import { filter, map, without, find, includes } from 'lodash' +import { useEnvSize, useScrollOnRouteChange, useSession, useTitle } from '@vnuge/vnlib.browser' +import CookieWarning from './components/CookieWarning.vue' +import PasswordPrompt from './components/PasswordPrompt.vue' +import siteHeader from './components/Header.vue' +import siteFooter from './components/Footer.vue' +import ConfirmPrompt from './components/ConfirmPrompt.vue' +import { headerRoutes, authRoutes, siteTitle, showCookieWarning } from './index' + +const { loggedIn } = useSession(); +const { getRoutes } = useRouter(); +const { title } = useTitle(siteTitle.value); + +//Use the env size to calculate the header and footer heights for us +const { header, footer, content, headerHeight, footerHeight } = useEnvSize(true) + +//setup autoscroll +useScrollOnRouteChange(); + +//Compute meta title from the default site title and the page title +const metaTile = computed(() => title.value ? `${title.value} | ${siteTitle.value}` : siteTitle.value) + +const routes = computed<RouteLocation[]>(() => { + // Get routes that are defined above but only if they are defined in the router + // This is a computed property because loggedin is a reactive property + const routeNames = loggedIn.value ? authRoutes.value : headerRoutes.value + + const routes = filter(getRoutes(), (pageName) => includes(routeNames, pageName.name)) + + const activeRoutes = map(routeNames, route => find(routes, { name: route })) + + return without<RouteLocation>(activeRoutes, undefined) +}) + + +//Forces the page content to be exactly the height of the viewport - header and footer sizes +const bodyStyle = computed(() => { + return { 'min-height': `calc(100vh - ${headerHeight.value + footerHeight.value}px)` } +}) + +const generalToastStyle = computed(() => { + return { top: `${headerHeight.value + 5}px` } +}) + +const formToastStyle = computed(() => { + return { top: `${headerHeight.value}px` } +}) +</script> + +<style> + +</style> diff --git a/front-end/src/bootstrap/components/ConfirmPrompt.vue b/front-end/src/bootstrap/components/ConfirmPrompt.vue new file mode 100644 index 0000000..8114387 --- /dev/null +++ b/front-end/src/bootstrap/components/ConfirmPrompt.vue @@ -0,0 +1,69 @@ + +<template> + <div id="confirm-prompt"> + <Dialog class="modal-entry" :style="style" :open="isRevealed" @close="cancel" > + <div class="modal-content-container"> + <DialogPanel> + <DialogTitle class="modal-title"> + {{ title }} + </DialogTitle> + + <DialogDescription class="modal-description"> + {{ message.text }} + </DialogDescription> + + <p class="modal-text-secondary"> + {{ message.subtext }} + </p> + + <div class="modal-button-container"> + <button class="rounded btn sm primary" @click="confirm"> + Confirm + </button> + <button class="rounded btn sm" @click="cancel"> + Close + </button> + </div> + </DialogPanel> + </div> + </Dialog> + </div> +</template> + +<script setup lang="ts"> +import { defaultTo } from 'lodash' +import { computed, ref } from 'vue' + +import { + Dialog, + DialogPanel, + DialogTitle, + DialogDescription, +} from '@headlessui/vue' + +import { onClickOutside } from '@vueuse/core' +import { useConfirm, useEnvSize } from '@vnuge/vnlib.browser' + +const { headerHeight } = useEnvSize() +//Use component side of confirm +const { isRevealed, confirm, cancel, onReveal } = useConfirm() + +const dialog = ref(null) +const message = ref({}) + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => isRevealed.value ? cancel() : null) + +//Set message on reveal +onReveal(m => message.value = m); + +const title = computed(() => defaultTo(message.value.title, 'Confirm')) + +const style = computed(() => { + return { + 'height': `calc(100vh - ${headerHeight.value}px)`, + 'top': `${headerHeight.value}px` + } +}) + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/components/CookieWarning.vue b/front-end/src/bootstrap/components/CookieWarning.vue new file mode 100644 index 0000000..b5239f5 --- /dev/null +++ b/front-end/src/bootstrap/components/CookieWarning.vue @@ -0,0 +1,36 @@ +<template> + <div v-if="show" class="fixed top-0 left-0 z-10 w-full" :style="style"> + <div class="flex w-full p-2 text-center text-white bg-blue-600"> + <div class="m-auto text-sm font-semibold md:text-base"> + You must have cookies enabled for this site to work properly + </div> + </div> + </div> +</template> + +<script setup lang="ts"> + +import { computed, toRefs } from 'vue' +import { useEnvSize } from '@vnuge/vnlib.browser' + +const props = defineProps<{ + hidden?: boolean +}>() + +const { hidden } = toRefs(props) + +const { headerHeight } = useEnvSize() + +const show = computed(() => (!window.navigator.cookieEnabled) && !hidden.value) + +const style = computed(() => { + return { + top: headerHeight.value + 'px' + } +}) + +</script> + +<style> + +</style> diff --git a/front-end/src/bootstrap/components/Footer.vue b/front-end/src/bootstrap/components/Footer.vue new file mode 100644 index 0000000..98f6f55 --- /dev/null +++ b/front-end/src/bootstrap/components/Footer.vue @@ -0,0 +1,69 @@ +<template> + <footer class="bottom-0 left-0 z-10 w-full"> + <div id="footer-content" class="footer-content" > + <div class="footer-main-container"> + <div class="col-span-4 sm:col-span-6 lg:col-span-3"> + <p class="my-4 text-xs leading-normal"> + No tracking, no external dependencies, no ads. You shouldn't trust anyone with your data, + and I am no exception. + </p> + </div> + <nav> + <slot name="footer-nav-1" /> + </nav> + <nav> + <slot name="footer-nav-2" /> + </nav> + <nav> + <p class="nav-title"> + Built with + </p> + <a class="footer-link" href="https://www.vaughnnugent.com/resources/software/modules">VNLib HTTP v1.0.1</a> + <a class="footer-link" href="https://tailwindcss.com/">Tailwindcss</a> + <a class="footer-link" href="https://vuejs.org/">Vuejs v3</a> + <a class="footer-link" href="https://fontawesome.com/">Font Awesome</a> + </nav> + <div class="color-selector-container"> + <p class="nav-title"> + Color Scheme + </p> + <div class="flex flex-row gap-6 md:my-auto"> + <div class=""> + <button class="bg-sel-btn" @click.prevent="Dark" > + Dark + </button> + </div> + <div class=""> + <fa-icon icon="lightbulb" /> + </div> + <div class=""> + <button class="bg-sel-btn" @click.prevent="Light"> + Light + </button> + </div> + </div> + </div> + </div> + <div class="text-sm footer-lower"> + <div class="mb-6 md:mb-0"> + <p class="text-left"> + Highly angular trousers ~ Pete Jordanson + </p> + </div> + <div class="mb-6 text-left md:mb-0"> + Copyright © 2023 Vaughn Nugent. All Rights Reserved. + </div> + </div> + </div> + </footer> +</template> + +<script setup lang="ts"> +import { useDark } from '@vueuse/core' +import { debounce } from 'lodash' + +const isDark = useDark() + +const Dark = debounce(() => isDark.value = true, 50) +const Light = debounce(() => isDark.value = false, 50) +</script> diff --git a/front-end/src/bootstrap/components/Header.vue b/front-end/src/bootstrap/components/Header.vue new file mode 100644 index 0000000..f7481a3 --- /dev/null +++ b/front-end/src/bootstrap/components/Header.vue @@ -0,0 +1,162 @@ +<!-- eslint-disable vue/max-attributes-per-line --> +<template> + <header class="sticky top-0 left-0 z-40"> + <div class="flex header-container"> + <div id="header-mobile-menu" ref="sideMenu" class="side-menu" :style="sideMenuStyle"> + <div class="pt-4 pl-4 pr-6"> + <nav id="header-mobile-nav" class="relative flex flex-col pr-3"> + <div v-for="route in routes" :key="route.path" class="m-auto ml-0"> + <div class="my-1" @click="closeSideMenu"> + <router-link :to="route"> + {{ route.name }} + </router-link> + </div> + </div> + </nav> + </div> + </div> + <div class="flex flex-row w-full md:mx-3"> + <div class="hidden w-4 lg:block" /> + <div class="flex px-4 my-auto text-xl md:hidden"> + <div v-if="!sideMenuActive" class="w-7" @click.prevent="openSideMenu"> + <fa-icon icon="bars" /> + </div> + <div v-else class="text-2xl w-7"> + <fa-icon icon="times" /> + </div> + </div> + <div id="site-title-container" class="flex m-0 mr-3"> + <div class="inline-block px-1"> + <slot name="site_logo" /> + </div> + <div id="site-title" class="inline-block m-auto mx-1"> + <router-link to="/"> + <h3>{{ siteTitle }}</h3> + </router-link> + </div> + </div> + <div class="hidden w-4 lg:block" /> + <nav id="header-desktop-nav" class="flex-row hidden mr-2 md:flex"> + <span v-for="route in routes" :key="route.fullPath" class="flex px-1 lg:px-3"> + <div v-if="!route.hide" class="m-auto"> + <router-link :to="route" class="flex-auto"> + {{ route.name }} + </router-link> + </div> + </span> + </nav> + <div id="user-menu" ref="userMenu" class="drop-controller" :class="{ 'hovered': userMenuHovered }"> + <div class="user-menu"> + Hello <span class="font-semibold">{{ uname }}</span> + </div> + <div ref="userDrop" class="absolute top-0 right-0 duration-100 ease-in-out" style="z-index:-1" :style="dropStyle"> + <div class="drop-menu" @click.prevent="userMenuHovered = false"> + <span class="space-x-2" /> + <a v-if="!loggedIn" href="#" @click="gotoRoute('/register')"> + Register + </a> + <a v-else href="#" @click="gotoRoute('/account')"> + Account + </a> + <a v-if="!loggedIn" href="#" @click="gotoRoute('/login')"> + Login + </a> + <a v-else href="#" @click.prevent="OnLogout"> + Logout + </a> + </div> + </div> + </div> + <div class="hidden space-x-4 lg:block" /> + </div> + </div> + </header> +</template> + +<script setup lang="ts"> + +import { debounce, find } from 'lodash' +import { useElementSize, onClickOutside, useElementHover } from '@vueuse/core' +import { computed, ref, toRefs } from 'vue' +import { useSession, useUser, useEnvSize, apiCall } from '@vnuge/vnlib.browser' +import { RouteLocation, useRouter } from 'vue-router'; + +const props = defineProps<{ + siteTitle: string, + routes: RouteLocation[] +}>() + +const { siteTitle, routes } = toRefs(props) + +const { loggedIn } = useSession() +const { userName, logout } = useUser() +const { headerHeight } = useEnvSize() + +//Get the router for navigation +const router = useRouter() + +const sideMenuActive = ref(false) + +const userDrop = ref(null) +const sideMenu = ref(null) +const userMenu = ref(null) + +const dropMenuSize = useElementSize(userDrop) +const sideMenuSize = useElementSize(sideMenu) +const userMenuHovered = useElementHover(userMenu) + +const uname = computed(() => userName.value || 'Visitor') +const sideMenuStyle = computed(() => { + // Side menu should be the exact height of the page and under the header, + // So menu height is the height of the page minus the height of the header + return { + height: `calc(100vh - ${headerHeight.value}px)`, + left: sideMenuActive.value ? '0' : `-${sideMenuSize.width.value}px`, + top: `${headerHeight.value}px` + } +}) + +const dropStyle = computed(() => { + return { + 'margin-top': userMenuHovered.value ? `${headerHeight.value}px` : `-${(dropMenuSize.height.value - headerHeight.value)}px` + } +}) + +const closeSideMenu = debounce(() => sideMenuActive.value = false, 50) +const openSideMenu = debounce(() => sideMenuActive.value = true, 50) + +//Close side menu when clicking outside of it +onClickOutside(sideMenu, closeSideMenu) + +//Redirect to the route when clicking on it +const gotoRoute = (route : string) =>{ + + //Get all routes from the router + const allRoutes = router.getRoutes(); + + //Try to find the route by its path + const goto = find(allRoutes, { path: route }); + + if(goto){ + //navigate to the route manually + router.push(goto); + } + else{ + //Fallback to full navigation + window.location.assign(route); + } +} + +const OnLogout = () =>{ + apiCall(async ({ toaster }) => { + await logout() + toaster.general.success({ + id: 'logout-success', + title: 'Success', + text: 'You have been logged out', + duration: 5000 + }) + }) +} + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/components/PasswordPrompt.vue b/front-end/src/bootstrap/components/PasswordPrompt.vue new file mode 100644 index 0000000..ae29358 --- /dev/null +++ b/front-end/src/bootstrap/components/PasswordPrompt.vue @@ -0,0 +1,110 @@ +<template> + <div id="password-prompt"> + <Dialog + class="modal-entry" + :style="style" + :open="isRevealed" + @close="close" + > + <div ref="dialog" class="modal-content-container" > + <DialogPanel> + <DialogTitle class="modal-title"> + Enter your password + </DialogTitle> + + <DialogDescription class="modal-description"> + Please re-enter your password to continue. + </DialogDescription> + + <form id="password-form" @submit.prevent="formSubmitted" :disabled="waiting"> + <fieldset> + <div class="input-container"> + <input v-model="v$.password.$model" type="password" class="rounded input primary" placeholder="Password" @input="onInput"> + </div> + </fieldset> + </form> + + <div class="modal-button-container"> + <button class="rounded btn sm primary" form="password-form"> + Submit + </button> + <button class="rounded btn sm" @click="close" > + Close + </button> + </div> + </DialogPanel> + </div> + </Dialog> + </div> +</template> + +<script setup lang="ts"> +import { onClickOutside } from '@vueuse/core' +import useVuelidate from '@vuelidate/core' +import { reactive, ref, computed } from 'vue' +import { helpers, required, maxLength } from '@vuelidate/validators' +import { useWait, useMessage, usePassConfirm, useEnvSize, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { Dialog, DialogPanel, DialogTitle, DialogDescription } from '@headlessui/vue' + +const { headerHeight } = useEnvSize() + +//Use component side of pw prompt +const { isRevealed, confirm, cancel } = usePassConfirm() + +const { waiting } = useWait() +const { onInput } = useMessage() + +//Dialog html ref +const dialog = ref(null) + +const pwState = reactive({ password: '' }) + +const rules = { + password: { + required: helpers.withMessage('Please enter your password', required), + maxLength: helpers.withMessage('Password must be less than 100 characters', maxLength(100)) + } +} + +const v$ = useVuelidate(rules, pwState, { $lazy: true }) + +//Wrap validator so we an display error message on validation, defaults to the form toaster +const { validate } = useVuelidateWrapper(v$); + +const style = computed(() => { + return { + 'height': `calc(100vh - ${headerHeight.value}px)`, + 'top': `${headerHeight.value}px` + } +}) + +const formSubmitted = async function () { + //Calls validate on the vuelidate instance + if (!await validate()) { + return + } + + //Store pw copy + const password = v$.value.password.$model; + + //Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Pass the password to the confirm function + confirm({ password }); +} + +const close = function () { + // Clear the password form + v$.value.password.$model = ''; + v$.value.$reset(); + + //Close prompt + cancel(null); +} + +//Cancel prompt when user clicks outside of dialog, only when its open +onClickOutside(dialog, () => isRevealed.value ? cancel() : null) + +</script>
\ No newline at end of file diff --git a/front-end/src/bootstrap/index.ts b/front-end/src/bootstrap/index.ts new file mode 100644 index 0000000..ead41e1 --- /dev/null +++ b/front-end/src/bootstrap/index.ts @@ -0,0 +1,113 @@ +import App from '../App.vue' +import Notifications, { notify } from '@kyvg/vue3-notification' +import { configureNotifier } from '@vnuge/vnlib.browser' +import { createApp as vueCreateApp, ref } from "vue"; +import { useDark } from "@vueuse/core"; + +//Font awesome support +import { Library } from "@fortawesome/fontawesome-svg-core"; +import { FontAwesomeIcon } from '@fortawesome/vue-fontawesome' +import { faBars, faLightbulb, faTimes } from '@fortawesome/free-solid-svg-icons' + + +//Required global state elements for app components +export const headerRoutes = ref<string[]>([]); +export const authRoutes = ref<string[]>([]); +export const siteTitle = ref<string>(""); +export const showCookieWarning = ref<boolean>(false); + + +export interface AppConfig { + /** + * Routes to be displayed in the header when the user is not logged in + */ + headerRoutes: string[]; + + /** + * Routes to be displayed in the header when the user is logged in + */ + authRoutes: string[]; + + /** + * The title of the site, used in the title bar + */ + siteTitle: string; + + /** + * Enables dark mode support + */ + useDarkMode: boolean; + + /** + * The element to mount the app to + */ + mountElement: string; + + /** + * library instance for adding required icons + */ + faLibrary: Library; + + /** + * If true, the cookie warning will not be displayed + */ + hideCookieWarning?: boolean; + + /** + * Called when the app is created for you to add custom elements + * and configure the app + * @param app The app instance + */ + onCreate: (app: any) => void; +} + +/** + * Creates a new vn-minimal vuejs web-app with the given configuration + * @param config The configuration for the app + * @returns The app instance + */ +export const createVnApp = (config: AppConfig, createApp?: (app: any) => any) => { + headerRoutes.value = config.headerRoutes; + authRoutes.value = config.authRoutes; + siteTitle.value = config.siteTitle; + + //Allow the user to override the createApp function + createApp = createApp || vueCreateApp; + + //Enable dark mode support + if (config.useDarkMode) { + useDark({ + selector: 'html', + valueDark: 'dark', + valueLight: 'light', + }); + } + + if (!config.hideCookieWarning) { + //Configure the cookie warning to be displayed cookies are not enabled + showCookieWarning.value = navigator?.cookieEnabled === false; + } + + //Add required icons to library + config.faLibrary.add(faBars, faLightbulb, faTimes); + + //create the vue app + const app = createApp(App) + + //Add the library to the app + app.component('fa-icon', FontAwesomeIcon) + + //Add the notification and router to the app + app.use(Notifications); + + //Call the onCreate callback + config.onCreate(app); + + //Mount the app + app.mount(config.mountElement); + + //Configure notification handler + configureNotifier({ notify, close: notify.close }); + + return app; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/all.scss b/front-end/src/bootstrap/style/all.scss new file mode 100644 index 0000000..ede0b30 --- /dev/null +++ b/front-end/src/bootstrap/style/all.scss @@ -0,0 +1,14 @@ +@tailwind base; + +@tailwind components; + +@tailwind utilities; + +@import "./buttons.scss"; +@import "./footer.scss"; +@import "./header.scss"; +@import "./inputs.scss"; +@import "./headings.scss"; +@import "./toast.scss"; +@import "./etc.scss"; +@import "./modals.scss";
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/buttons.scss b/front-end/src/bootstrap/style/buttons.scss new file mode 100644 index 0000000..a440def --- /dev/null +++ b/front-end/src/bootstrap/style/buttons.scss @@ -0,0 +1,44 @@ +.button-group { + @apply inline-flex -space-x-0 divide-x overflow-hidden rounded-lg border border-transparent shadow-sm; + @apply divide-gray-300 border-gray-300 dark:divide-dark-500 dark:border-dark-500; + + & .btn { + @apply border-0 ring-0 focus:ring-0; + } +} + +.btn { + @apply ease-in-out duration-100 border-2 px-4 py-2 text-center text-sm font-medium transition-all focus:ring-2; + @apply bg-white border-gray-300 text-gray-700 shadow-sm hover:bg-gray-100 focus:ring-gray-100; + + .dark & { + @apply bg-transparent text-inherit border-dark-300 hover:bg-transparent hover:border-gray-400 focus:ring-gray-300; + } + + &.borderless, + &.no-border, + &.b-0{ + @apply bg-transparent border-0 shadow-none ring-0 focus:ring-0 active:ring-0; + } + + &:disabled { + @apply cursor-not-allowed border-gray-100 bg-gray-50 text-gray-400; + @apply dark:bg-transparent dark:border-dark-400 dark:text-dark-300; + } + + &.sm { + @apply px-3 py-1.5 text-sm; + } + + &.xs { + @apply px-2 py-1; + } + + &.lg { + @apply px-5 py-3 text-lg; + } + + &.xl { + @apply px-6 py-4 text-xl; + } +} diff --git a/front-end/src/bootstrap/style/etc.scss b/front-end/src/bootstrap/style/etc.scss new file mode 100644 index 0000000..5d65ff4 --- /dev/null +++ b/front-end/src/bootstrap/style/etc.scss @@ -0,0 +1,67 @@ +#header-mobile-nav a:hover, +#header-desktop-nav a:hover, +#header-mobile-nav .router-link-active, +#header-desktop-nav .router-link-active, +footer .footer-content .router-link-active { + @apply text-primary-500 dark:text-primary-600; +} + +#header-mobile-nav a { + @apply text-2xl; +} + +#site-title h3 { + @apply mb-0 text-xl; +} + + +.env-bg { + font-family: "Nunito"; + background: #f7f7f7; + @apply dark:bg-dark-900; + @apply text-gray-700 dark:text-gray-300; +} + +.env-bg-gradient { + background: #98E4C8; + background: -webkit-linear-gradient(bottom right, #98E4C8, #2C6BC3); + background: -moz-linear-gradient(bottom right, #98E4C8, #2C6BC3); + background: linear-gradient(to top left, #98E4C8, #2C6BC3); + @apply text-gray-700; +} + +.app-component-entry { + @apply flex flex-col flex-auto mb-10; + + @screen sm { + & { + min-height: 640px; + } + } +} + +.vue-notification-group.general-toast { + @apply right-5; +} + +.float-label { + @apply mt-5 relative; + + &>label { + @apply absolute top-0 left-0 bottom-0 block pl-3 ease-linear duration-75 opacity-0; + } + + &>input:focus+label { + @apply opacity-100 -mt-6; + } +} + +.default-page-template { + min-height: 400px; + @apply container w-full max-w-4xl px-4 pt-3 mx-auto sm:pt-6 md:px-0; +} + +a.link { + @apply duration-150 ease-in-out; + @apply text-primary-500 hover:text-primary-600; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/footer.scss b/front-end/src/bootstrap/style/footer.scss new file mode 100644 index 0000000..4d05928 --- /dev/null +++ b/front-end/src/bootstrap/style/footer.scss @@ -0,0 +1,67 @@ +footer{ + @apply text-center shadow-md bg-white dark:bg-dark-800 dark:text-gray-500; + + .footer-content{ + @apply mx-auto max-w-7xl p-4; + + nav, + .color-selector-container{ + @apply col-span-4 sm:col-span-3 md:col-span-2 lg:col-span-2 ml-8 sm:ml-0; + } + .footer-link.router-link-active { + @apply text-primary-500 dark:text-primary-600; + } + + .footer-main-container { + @apply grid mb-3 sm:pl-0; + @apply grid-cols-4 sm:grid-cols-6 md:grid-cols-6 lg:grid-cols-11 gap-10 lg:gap-20; + } + + p.nav-title { + @apply mb-3 text-xs font-semibold tracking-wider uppercase text-left; + } + + a.footer-link { + @apply flex mb-3 text-sm font-medium md:mb-2 text-center transform hover:scale-105 ease-in-out duration-75; + @apply hover:text-primary-500 dark:hover:text-primary-600; + } + + button.bg-sel-btn { + @apply text-sm ease-in-out duration-100 transform hover:scale-105; + @apply hover:text-primary-500 dark:hover:text-primary-600; + } + + .footer-lower { + @apply flex flex-col flex-wrap items-start justify-between pt-10 mt-10 border-t md:flex-row md:items-center dark:border-dark-500; + } + } + + .env-bg-gradient &{ + @apply text-current text-gray-700 bg-transparent; + .footer-content { + border-top: 1px solid; + @apply border-gray-200; + + .nav-title { + @apply text-gray-700; + } + + nav .footer-link, + button.bg-sel-btn{ + @apply hover:text-white; + + &.router-link-active { + @apply text-white; + } + } + + button.bg-sel-btn{ + @apply hover:text-white; + } + } + + .footer-lower { + @apply border-t-0; + } + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/header.scss b/front-end/src/bootstrap/style/header.scss new file mode 100644 index 0000000..bc5789d --- /dev/null +++ b/front-end/src/bootstrap/style/header.scss @@ -0,0 +1,40 @@ +header{ + .header-container{ + @apply h-12; + } + .header-container, + .side-menu{ + @apply shadow-md; + @apply bg-white dark:bg-dark-800 dark:text-gray-100; + } + + .side-menu { + @apply absolute pt-2 ease-in-out duration-150; + } + + .drop-controller { + min-width: 11rem; + @apply hidden md:flex mr-0 ml-auto my-0 relative; + + .drop-menu{ + @apply w-36 m-auto cursor-pointer text-left rounded-b-lg flex flex-col; + + @apply text-gray-700 bg-white dark:bg-dark-800 dark:text-gray-300; + + @apply bg-white border-b border-l border-r border-transparent; + + a { + @apply px-3 py-1; + @apply text-gray-700 hover:text-black dark:text-gray-300 dark:hover:text-gray-100; + } + } + + &.hovered .drop-menu { + @apply border-gray-200 shadow-md dark:border-dark-500; + } + } + + .user-menu{ + @apply m-auto cursor-default truncate whitespace-nowrap max-w-xs; + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/headings.scss b/front-end/src/bootstrap/style/headings.scss new file mode 100644 index 0000000..326a7c2 --- /dev/null +++ b/front-end/src/bootstrap/style/headings.scss @@ -0,0 +1,32 @@ +h1, +h2, +h3, +h4, +h5, +h6 { + @apply font-medium leading-tight mt-0 mb-2; +} + +h1 { + @apply sm:text-5xl text-4xl; +} + +h2 { + @apply sm:text-4xl text-3xl; +} + +h3 { + @apply sm:text-3xl text-2xl; +} + +h4 { + @apply sm:text-2xl text-xl; +} + +h5 { + @apply sm:text-xl text-lg; +} + +h6 { + @apply sm:text-base text-sm; +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/inputs.scss b/front-end/src/bootstrap/style/inputs.scss new file mode 100644 index 0000000..64f8901 --- /dev/null +++ b/front-end/src/bootstrap/style/inputs.scss @@ -0,0 +1,65 @@ +input.input, +select.input, +textarea.input { + @apply duration-100 ease-in-out outline-none border p-2; + @apply border-gray-200 bg-inherit dark:border-dark-400 dark:text-white hover:border-gray-300 hover:dark:border-dark-200; +} + + +/* CHECKBOXES */ + +label.checkbox { + @apply flex items-center cursor-pointer; + + input[type="checkbox"] { + @apply ease-in-out duration-100 w-5 h-5; + @apply border-2 rounded-sm border-gray-300 dark:border-dark-500; + + &:checked { + @apply text-primary-500 dark:text-primary-600 border-primary-500 dark:border-primary-600; + } + } + + &.primary { + input[type="checkbox"] { + @apply appearance-none; + @apply hover:border-primary-500 dark:hover:border-primary-600; + + &:checked { + @apply bg-primary-500 dark:bg-primary-600 border-primary-500 dark:border-primary-600; + } + + &+span.check { + clip-path: polygon(14% 44%, 0 65%, 50% 100%, 100% 16%, 80% 0%, 43% 62%); + @apply bg-white dark:bg-dark-500; + } + } + + span.check { + margin: 0px 0px 1px 4px; + @apply absolute h-3 w-3; + } + } + +} + +/*Select */ + +select.input.options { + @apply text-current; +} + +select.input:disabled{ + @apply appearance-none; +} + +/*Validation inputs*/ +input.input.dirty.data-valid, +select.input.dirty.data-valid { + @apply border-primary-500 dark:border-primary-600; +} + +input.input.dirty.data-invalid, +select.input.dirty.data-invalid { + @apply border-red-600 dark:border-red-500; +} diff --git a/front-end/src/bootstrap/style/modals.scss b/front-end/src/bootstrap/style/modals.scss new file mode 100644 index 0000000..254b8e1 --- /dev/null +++ b/front-end/src/bootstrap/style/modals.scss @@ -0,0 +1,29 @@ +.modal-entry { + background: #00000077; + @apply fixed z-50 flex w-full px-6; + + .modal-content-container { + @apply w-full max-w-md p-5 m-auto rounded-md shadow-2xl mt-44; + @apply bg-white border border-transparent dark:bg-dark-600 dark:border-primary-500 dark:text-white; + + .modal-title { + @apply text-xl font-bold; + } + + .modal-description { + @apply text-sm; + } + } + + .modal-button-container { + @apply flex flex-row justify-end pt-3 gap-3; + } + + .input-container { + @apply pt-5; + + input { + @apply w-full; + } + } +}
\ No newline at end of file diff --git a/front-end/src/bootstrap/style/toast.scss b/front-end/src/bootstrap/style/toast.scss new file mode 100644 index 0000000..3ddc3ac --- /dev/null +++ b/front-end/src/bootstrap/style/toast.scss @@ -0,0 +1,26 @@ +.general-toast { + .notification-title { + font-size: 16px; + } + + .notification-content { + font-size: 14px; + } + + .vue-notification { + @apply duration-200 ease-in-out shadow-md hover:shadow-lg; + } +} + +.form-toast { + left: calc(50% - 150px); + @apply pt-2 mx-auto mb-3; + + .notification-title { + font-size: 14px; + } + + .notification-content { + font-size: 12px; + } +} diff --git a/front-end/src/components/DynamicForm.vue b/front-end/src/components/DynamicForm.vue new file mode 100644 index 0000000..137ca3b --- /dev/null +++ b/front-end/src/components/DynamicForm.vue @@ -0,0 +1,92 @@ +<template> + + <form :id="form.id" class="dynamic-form form" :path="path" @submit.prevent="onSubmit"> + + <fieldset class="dynamic-form input-group" :disabled="disabled"> + + <!-- Create a new div element for each field in the form --> + <div v-show="!field.hidden" + v-for="field in fields" + :key="field" + :class="{ 'dirty': field.validator.$dirty, 'data-invalid': field.validator.$invalid }" + class="dynamic-form input-container" + > + <!-- label above the fields --> + <label :for="field.id" class="dynamic-form input-label" > + {{ field.label }} + </label> + + <!-- Determine select, input, or textarea --> + <select v-if="isSelect(field)" + v-model="field.validator.$model" + :id="field.id" + :disabled="field.disabled" + class="dynamic-form dynamic-input input-select" + @change="onInput(field)" + > + + <option v-for="option in field.options" :key="option.value" :value="option.value"> + {{ option.label }} + </option> + + </select> + + <textarea v-else-if="isTextArea(field)" + v-model="field.validator.$model" + :id="field.id" + :disabled="field.disabled" + class="dynamic-form dynamic-input input-textarea" + @input="onInput(field)" + /> + + <input v-else + v-model="field.validator.$model" + :id="field.id" + :type="field.type" + :name="field.name" + :disabled="field.disabled" + class="dynamic-form dynamic-input input" + :placeholder="placeholder(field)" + @input="onInput(field)" + > + + <div class="dynamic-form field-description"> + <p>{{ field.description }}</p> + </div> + </div> + </fieldset> + </form> +</template> + +<script setup lang="ts"> +import { defaultTo, cloneDeep, forEach } from 'lodash' +import { toRefs, computed } from 'vue' + +const props = defineProps<{ + form: any + disabled?: boolean + validator: any +}>() + +const emit = defineEmits(['input', 'submit']) + +const { form, disabled, validator } = toRefs(props) + +const schema = computed(() => cloneDeep(form.value)) +const path = computed(() => defaultTo(form.value.path, '#')) + +const fields = computed(() =>{ + const ff = defaultTo(schema.value.fields, []) + //Set validators for the field, storeing the fields in the schema item + forEach(ff, field => field.validator = validator.value[field.name]) + return ff; +}) + +const isSelect = (field : any) => field.type === 'select' +const isTextArea = (field : any) => field.type === 'textarea' +const placeholder = (field : any) => defaultTo(field.placeholder, field.label) + +const onSubmit = () => emit('submit') +const onInput = (field : string) => emit('input', field) + +</script>
\ No newline at end of file diff --git a/front-end/src/components/FooterNav1.vue b/front-end/src/components/FooterNav1.vue new file mode 100644 index 0000000..2bc5b28 --- /dev/null +++ b/front-end/src/components/FooterNav1.vue @@ -0,0 +1,11 @@ +<template> + <div> + + </div> +</template> +<script setup lang="ts"> + +</script> +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/front-end/src/components/FooterNav2.vue b/front-end/src/components/FooterNav2.vue new file mode 100644 index 0000000..9a50e58 --- /dev/null +++ b/front-end/src/components/FooterNav2.vue @@ -0,0 +1,21 @@ +<template> + <p class="nav-title"> + Account + </p> + <router-link class="footer-link" to="/login" > + Login + </router-link> + <router-link class="footer-link" to="/register"> + Regsiter + </router-link> + <router-link class="footer-link" to="/account"> + Profile + </router-link> + <router-link class="footer-link" to="/account/settings"> + Settings + </router-link> +</template> +<script setup lang="ts"> + +</script> +<style lang="scss"></style>
\ No newline at end of file diff --git a/front-end/src/components/Site-Logo.vue b/front-end/src/components/Site-Logo.vue new file mode 100644 index 0000000..c1e28d0 --- /dev/null +++ b/front-end/src/components/Site-Logo.vue @@ -0,0 +1,7 @@ +<template> + <div></div> +</template> + +<script setup lang="ts"> + +</script>
\ No newline at end of file diff --git a/front-end/src/main.ts b/front-end/src/main.ts new file mode 100644 index 0000000..8acd355 --- /dev/null +++ b/front-end/src/main.ts @@ -0,0 +1,119 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + + +//Get the create app from boostrap dir +import { createVnApp } from './bootstrap' + +//Import all styles +import './bootstrap/style/all.scss' +import './assets/main.scss' + +//Use the Nunito font +import "@fontsource/nunito" + +/* FONT AWESOME CONFIG */ +import { library } from '@fortawesome/fontawesome-svg-core' +import { faBullhorn, faCertificate, faCheck, faChevronLeft, faChevronRight, faComment, faCopy, faFolderOpen, faKey, faLink, faMinusCircle, faPencil, faPhotoFilm, faPlus, faRotateLeft, faSignInAlt, faSpinner, faSync, faTrash, faUser } from '@fortawesome/free-solid-svg-icons' +import { faGithub, faDiscord, faMarkdown } from '@fortawesome/free-brands-svg-icons' + +//Add required icons for the app +library.add(faSignInAlt, faGithub, faDiscord, faSpinner, faCertificate, faKey, faSync, faPlus, faMinusCircle, faUser, faCheck, faTrash, faCopy, + faPencil, faLink, faPhotoFilm, faRotateLeft, faMarkdown, faBullhorn, faFolderOpen, faComment, faChevronLeft, faChevronRight); + +//Add icons to library +import router from './router' + +//Import nav components +import FooterNav1 from './components/FooterNav1.vue' +import FooterNav2 from './components/FooterNav2.vue' +import SiteLogo from './components/Site-Logo.vue' +import DynamicFormVue from './components/DynamicForm.vue' +import { configureApi, useAutoHeartbeat } from '@vnuge/vnlib.browser' +import { useLocalStorage } from '@vueuse/core' +import { watch } from 'vue' + +createVnApp({ + //The app mount point + mountElement: '#app', + + //The site title + siteTitle: 'CMNext Admin', + + //Routes to display in the header when the user is not logged in + headerRoutes: ['Home', 'Login'], + + //Routes to display in the header when the user is logged in + authRoutes: ['Home', 'Blog', 'Account', 'Login'], + + //Enable dark mode support + useDarkMode: true, + + //Add the font awesome library + faLibrary: library, + + //Called when the app is created for you to add custom elements + onCreate(app) { + + //Add the router + app.use(router) + + //Configure account page redirect to profile + router.addRoute({ + path: '/account', + name: 'Account', + redirect: { path: '/account/profile' } + }) + + //Add the footer nav components + app.component('FooterNav1', FooterNav1) + app.component('FooterNav2', FooterNav2) + + //Register site-logo component + app.component('SiteLogo', SiteLogo) + + //Register the dynamic form component + app.component('dynamic-form', DynamicFormVue) + }, +}) + +//Setup the vnlib api +configureApi({ + session: { + cookiesEnabled: navigator.cookieEnabled, + loginCookieName: import.meta.env.VITE_LOGIN_COOKIE_ID, + bidSize: 32, + storage: localStorage + }, + user: { + accountBasePath: import.meta.env.VITE_ACCOUNTS_BASE_PATH, + storage: localStorage, + //The heartbeat interval in milliseconds, if you enable it + autoHearbeatInterval: 1000 * 60 * 5, //5 minute interval + }, + axios: { + baseURL: import.meta.env.VITE_API_URL, + withCredentials: import.meta.env.VITE_CORS_ENABLED === 'true', + tokenHeader: import.meta.env.VITE_WEB_TOKEN_HEADER, + } +}) + +//Get shared global state storage +const mainState = useLocalStorage("vn-state", { ahEnabled: false }); + +//Setup interval from local storage that remembers the user's preferrence +const { enabled } = useAutoHeartbeat(mainState.value.ahEnabled) +//Update the local storage when the value changes +watch(enabled, (val) => mainState.value.ahEnabled = val)
\ No newline at end of file diff --git a/front-end/src/router/index.ts b/front-end/src/router/index.ts new file mode 100644 index 0000000..d5685b5 --- /dev/null +++ b/front-end/src/router/index.ts @@ -0,0 +1,8 @@ +import { createRouter, createWebHistory } from 'vue-router' + +import routes from '~pages' + +export default createRouter({ + history: createWebHistory(import.meta.env.BASE_URL), + routes +})
\ No newline at end of file diff --git a/front-end/src/views/Account/[comp].vue b/front-end/src/views/Account/[comp].vue new file mode 100644 index 0000000..75fd086 --- /dev/null +++ b/front-end/src/views/Account/[comp].vue @@ -0,0 +1,136 @@ +<template> + <div id="account-template" class="app-component-entry"> + <TabGroup :selectedIndex="tabId" @change="onTabChange" as="div" class="container h-full m-auto mt-0 mb-10 duration-150 ease-linear"> + + <div class="flex w-full py-2 xl:w-auto lg:pt-4 xl:fixed"> + + <TabList as="div" class="flex flex-row mx-auto mb-1 xl:mx-0 xl:mb-0"> + + <Tab v-slot="{ selected }" > + <span class="page-link" :class="{ 'active': selected }"> + Profile + </span> + </tab> + + <Tab v-slot="{ selected }" > + <span class="page-link" :class="{ 'active': selected }"> + OAuth + </span> + </tab> + + <Tab v-slot="{ selected }" > + <span class="page-link" :class="{ 'active': selected }"> + Settings + </span> + </tab> + + </TabList> + </div> + + <TabPanels as="div" class="xl:my-16 md:mb-4"> + + <TabPanel :unmount="false"> + <Profile /> + </TabPanel> + + <TabPanel :unmount="false"> + <OauthApps /> + </TabPanel> + + <TabPanel :unmount="false"> + <Settings /> + </TabPanel> + + </TabPanels> + + </TabGroup> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue' +import { usePageGuard, useTitle } from '@vnuge/vnlib.browser' +import { useRouteParams } from '@vueuse/router' +import { TabGroup, TabList, Tab, TabPanels, TabPanel } from '@headlessui/vue' +import Settings from './components/settings/Settings.vue' +import Profile from './components/profile/Profile.vue' +import OauthApps from './components/oauth/Oauth.vue' + +usePageGuard() +useTitle('Account') + +enum ComponentType{ + Profile = 'profile', + Oauth = 'oauth', + Settings = 'settings' +} + +const comp = useRouteParams<ComponentType>('comp') + +const tabId = computed<number>(() =>{ + switch (comp.value) { + case ComponentType.Oauth: + return 1 + case ComponentType.Settings: + return 2 + case ComponentType.Profile: + default: + return 0 + } +}) + +const onTabChange = (tabid : number) =>{ + switch (tabid) { + case 1: + comp.value = ComponentType.Oauth + break + case 2: + comp.value = ComponentType.Settings + break + case 0: + default: + comp.value = ComponentType.Profile + break + } +} + +</script> + +<style lang="scss"> +#account-template{ + p{ + @apply text-gray-700 dark:text-gray-400; + } + + .page-link{ + font-size: 1.1rem; + @apply border-b-2 border-transparent cursor-pointer mx-2 px-1; + } + + .page-link.active{ + @apply border-primary-500; + } + + .acnt-content-container{ + @apply m-auto max-w-3xl; + } + + .panel-container{ + + } + + .panel-container .panel-header{ + @apply flex flex-row px-2; + } + + .panel-container .panel-content{ + @apply bg-white dark:bg-dark-800 border-transparent dark:border-dark-500; + @apply m-auto max-w-3xl border sm:rounded-md shadow-md sm:p-4 p-3 sm:my-3 my-2; + } + + .panel-container .panel-header .panel-title{ + @apply my-auto; + } +} + +</style> diff --git a/front-end/src/views/Account/components/oauth/CreateApp.vue b/front-end/src/views/Account/components/oauth/CreateApp.vue new file mode 100644 index 0000000..2321743 --- /dev/null +++ b/front-end/src/views/Account/components/oauth/CreateApp.vue @@ -0,0 +1,183 @@ +<template> + <Dialog :open="isOpen" @close="close" class="relative z-10"> + <div class="fixed inset-0 bg-black/30" aria-hidden="true" /> + <div class="fixed inset-0 flex justify-center top-20"> + <DialogPanel class="new-o2-app-dialog"> + <DialogTitle>Create app</DialogTitle> + <div class="flex"> + <div class="m-auto mb-3 text-sm"> + <p class="my-1"> + Step 1: Enter a name for your app. + </p> + <p class="my-1"> + Step 2: Submit the form. + </p> + <p class="my-1 text-red-500"> + Step 3: Save your Client ID and Secret somewhere safe. + </p> + </div> + </div> + <!-- If secret is set, show the scret window --> + <div v-if="newAppBuffer.secret" class="mt-2"> + <div class="block mx-1 sm:inline"> + Secret: + </div> + <div class="px-1 py-4 my-2 break-all border-2 border-gray-300 rounded-lg"> + <div class="text-center secret"> + <span class="block mx-1 sm:inline"> + {{ newAppBuffer.secret }} + </span> + </div> + </div> + <div class="text-sm"> + <p class="p-2"> + This secret will only be displayed <strong>once</strong>, and you cannot request it again. + If you lose it, you will need to update the secret from the app edit pane. + </p> + <p class="p-2"> + Please remember to keep this secret somewhere safe. If an attacker gains + access to it, they will be able to access any APIs on your behalf! + </p> + </div> + <div class="flex justify-end"> + <button v-if="!copied" class="btn primary" @click="copy(newAppBuffer.secret)"> + Copy + </button> + <button v-else class="btn primary" @click="close"> + Done + </button> + </div> + </div> + <div v-else> + <form id="o2-app-creation" class="" @submit.prevent="onFormSubmit"> + <fieldset class="flex flex-col gap-4"> + <div class="input-container"> + <label>App Name</label> + <input + class="w-full mt-1 input primary" + :class="{'invalid':v$.name.$invalid, 'dirty': v$.name.$dirty}" + name="name" + type="text" + v-model="v$.name.$model" + /> + </div> + <div class="input-container"> + <label>Description</label> + <textarea + class="w-full mt-1 input primary" + :class="{ 'invalid': v$.description.$invalid, 'dirty': v$.name.$dirty }" + name="description" + v-model="v$.description.$model" + rows="3" + /> + </div> + <div class="input-container"> + <label>Permissions</label> + <div class="flex flex-col flex-wrap sm:flex-row"> + <div v-for="permission in appPermissions" :key="permission.type" class="my-2 sm:m-3"> + <label class="flex cursor-pointer"> + <input class="w-5 cursor-pointer" type="checkbox" :name="permission.type" @change="permissionChanged"> + <span class="pl-1">{{ permission.label }}</span> + </label> + </div> + </div> + </div> + </fieldset> + <div class="flex justify-end mt-4"> + <div class="button-group"> + <button type="submit" form="o2-app-creation" class="btn primary">Submit</button> + <button class="btn" @click.prevent="close">Cancel</button> + </div> + </div> + </form> + </div> + </DialogPanel> + </div> + </Dialog> +</template> + +<script setup lang="ts"> +import { indexOf, pull } from 'lodash' +import { ref, toRefs } from 'vue'; +import { Dialog, DialogPanel, DialogTitle } from '@headlessui/vue' +import { apiCall } from '@vnuge/vnlib.browser' +import { useOAuth2Apps, getAppValidator, getAppPermissions } from './o2Api' +import { useClipboard } from '@vueuse/core' + +const emit = defineEmits(['close']) + +const props = defineProps<{ + isOpen: boolean +}>() + +const { isOpen } = toRefs(props); + +const { copied, copy } = useClipboard(); +//Init the oauth2 app api +const { createApp } = useOAuth2Apps('/oauth/apps'); +const appPermissions = getAppPermissions(); + +const newAppBuffer = ref({}); +const newAppPermissions = ref([]); + +const { v$, validate, reset } = getAppValidator(newAppBuffer); + +const close = () => { + newAppBuffer.value = {} + reset() + emit('close') +} + +const onFormSubmit = async () =>{ + + // Validate the new app form + if (!await validate()) { + return + } + + // Create the new app + await apiCall(async () => { + + const { secret } = await createApp(newAppBuffer.value) + + // Reset the new app buffer and pass the secret value + newAppBuffer.value = { secret } + }) + + // reset the validator + v$.value.$reset() +} + +const permissionChanged = (e : any) => { + if (e.target.checked) { + // Make sure the permission is not already in the list + if (indexOf(newAppPermissions.value, e.target.name) > -1) { + return + } + // Add the permission to the list + newAppPermissions.value.push(e.target.name) + } else { + // Remove the permission from the list + pull(newAppPermissions.value, e.target.name) + } + // Update the permissions model + v$.value.permissions.$model = newAppPermissions.value.join(',') +} + +</script> + +<style lang="scss"> + +.new-o2-app-dialog{ + @apply w-full max-w-lg p-8 pt-4 m-auto mt-0 shadow-md sm:rounded-md; + @apply bg-white dark:bg-dark-600 dark:text-gray-200; + + #o2-app-creation{ + input.dirty.invalid, + textarea.dirty.invalid{ + @apply border-red-500 focus:border-red-500; + } + } +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Account/components/oauth/Oauth.vue b/front-end/src/views/Account/components/oauth/Oauth.vue new file mode 100644 index 0000000..119aa50 --- /dev/null +++ b/front-end/src/views/Account/components/oauth/Oauth.vue @@ -0,0 +1,93 @@ +<template> + <div id="oauth-apps" class="acnt-content-container"> + <div class="app-container panel-container"> + <div class="mb-6 panel-header"> + <div class="flex ml-0 mr-auto"> + <div class="my-auto panel-title"> + <h4>Your applications</h4> + </div> + </div> + <div class="ml-auto mr-0"> + <div class="button-container"> + <button class="btn primary sm" :disabled="!isLocalAccount" @click="editNew = true"> + Create App + </button> + </div> + </div> + </div> + <div v-if="apps?.length == 0" class="no-apps-container"> + <div class="m-auto"> + You dont have any OAuth2 client applications yet. + </div> + </div> + <div v-else> + <div v-for="app in apps" :key="app.data.Id" class="panel-content"> + <SingleApplication :application="app" :allow-edit="isLocalAccount" @appDeleted="loadApps" /> + </div> + </div> + </div> + <div class="px-2 my-10"> + <div class="m-auto text-sm"> + OAuth2 applications allow you grant api access to OAuth2 clients using the Client Credentials grant type. + <a class="link" href="https://oauth.net" target="_blank"> + Learn more + </a> + </div> + <div v-show="!isLocalAccount" class="mt-3 text-center text-red-500"> + You may not create or edit applications if you are using external authentication. + </div> + </div> + <CreateApp :is-open="editNew" @close="newAppClose" /> + </div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue' +import CreateApp from './CreateApp.vue' +import { useSession, apiCall } from '@vnuge/vnlib.browser' + +import SingleApplication from './SingleApplication.vue' +import { AppBuffer, OAuth2Application, useOAuth2Apps } from './o2Api' + +const { isLocalAccount } = useSession() +const { getApps } = useOAuth2Apps('/oauth/apps'); + +const apps = ref<AppBuffer<OAuth2Application>[]>(); +const editNew = ref(false); + +const loadApps = async () => { + await apiCall(async () => { + const appList = await getApps(); + // sort apps from newest to oldest + appList.sort((a, b) => { + if (a.data.Created > b.data.Created) return -1 + if (a.data.Created < b.data.Created) return 1 + return 0 + }) + // set the apps + apps.value = appList + }) +} + +const newAppClose = () => { + editNew.value = false; + //Reload apps on close + loadApps(); +} + +//Load apps, but do not await +loadApps() + +</script> + +<style> + +#oauth-apps { + @apply m-auto max-w-3xl; +} + +#oauth-apps .app-container .no-apps-container { + @apply w-full flex h-36 sm:border sm:rounded-md mt-4 mb-20 dark:border-dark-500 border-gray-300; +} + +</style> diff --git a/front-end/src/views/Account/components/oauth/SingleApplication.vue b/front-end/src/views/Account/components/oauth/SingleApplication.vue new file mode 100644 index 0000000..9fcc5e3 --- /dev/null +++ b/front-end/src/views/Account/components/oauth/SingleApplication.vue @@ -0,0 +1,190 @@ +<template> + <div :id="data.Id"> + <div class="flex flex-row"> + <div class="flex ml-0 mr-auto"> + <div class="flex w-8 h-8 rounded-full bg-primary-500"> + <div class="m-auto text-white dark:text-dark-500"> + <fa-icon icon="key"></fa-icon> + </div> + </div> + <div class="inline my-auto ml-2"> + <h5 class="m-0">{{ name }}</h5> + </div> + </div> + <div v-if="allowEdit && editMode" class="button-group"> + <button class="btn primary xs" :disabled="modified" @click="onSubmit">Update</button> + <button class="btn xs" @click="onCancel">Cancel</button> + </div> + <div v-else class=""> + <button class="btn no-border xs" @click="editMode = true">Edit</button> + </div> + </div> + <div class="px-3 py-1 text-gray-500"> + <div class="my-1"> + <span> Client ID: </span> + <span class="font-mono text-black dark:text-white">{{ clientId }}</span> + </div> + <div class="text-sm"> + <span> Created: </span> + <span>{{ createdTime }}</span> + </div> + <div v-if="!editMode" class="text-sm"> + <span>{{ data.description }}</span> + </div> + </div> + <div v-if="newSecret" class="flex"> + <div class="max-w-md py-4 mx-auto"> + <div class="pl-1 mb-2"> + New secret + </div> + <div class="p-4 text-sm break-all border-2 rounded-lg dark:border-dark-400"> + {{ newSecret }} + </div> + <div class="flex justify-end my-3"> + <button v-if="!copied" class="rounded btn" @click="copy(newSecret)"> + Copy + </button> + <button v-else class="rounded btn" @click="closeNewSecret"> + Done + </button> + </div> + </div> + </div> + <div v-else-if="editMode" class="app-form-container"> + <div class="py-4"> + <form :id="formId" class="max-w-md mx-auto"> + <fieldset :disabled="waiting" class=""> + <div class="input-container"> + <div class="pl-1 mb-1"> + App name + </div> + <input class="w-full input primary" :class="{ 'invalid': v$.name.$invalid }" v-model="v$.name.$model" type="text" name="name" /> + </div> + <div class="mt-3 input-container"> + <div class="pl-1 mb-1"> + App description + <span class="text-sm">(optional)</span> + </div> + <textarea class="w-full input primary" :class="{ 'invalid': v$.description.$invalid }" v-model="v$.description.$model" name="description" rows="3" /> + </div> + </fieldset> + </form> + </div> + <div class="mt-3"> + <div class="flex flex-row justify-center gap-3 mx-auto"> + <div class=""> + <button class="w-full btn yellow" @click="updateSecret"> + Update Secret + </button> + </div> + <div class=""> + <button class="w-full btn red" @click="onDelete"> + Delete + </button> + </div> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { toUpper } from 'lodash' +import { apiCall, useWait, useConfirm, usePassConfirm } from '@vnuge/vnlib.browser' +import { ref, computed, toRefs } from 'vue' +import { useClipboard, useTimeAgo } from '@vueuse/core' +import { useOAuth2Apps, getAppValidator, AppBuffer, OAuth2Application } from './o2Api' + +const props = defineProps<{ + application: AppBuffer<OAuth2Application> + allowEdit: boolean +}>() + +const emit = defineEmits(['secretUpdated', 'AppDeleted']) + +const { application, allowEdit } = toRefs(props) +const { data, buffer, revert, modified } = application.value; + +const { waiting } = useWait() +const { reveal } = useConfirm() +const { elevatedApiCall } = usePassConfirm() +const { copied, copy } = useClipboard() +const { deleteApp, updateAppMeta, updateAppSecret } = useOAuth2Apps('/oauth/apps'); + +const { v$, validate, reset } = getAppValidator(buffer) + +const editMode = ref(false) +const newSecret = ref<string | null>(null); + +const name = computed(() => data.name) +const clientId = computed(() => toUpper(data.client_id)) +const createdTime = useTimeAgo(data.Created); +const formId = computed(() => `app-form-${data.client_id}`) + +const onCancel = function () { + revert() + reset() + editMode.value = false +} + +const onSubmit = async function () { + // Validate the new app form + if (!await validate()) { + return + } + // Create the new app + await apiCall(async ({ toaster }) => { + // Update does not return anything, if successful + await updateAppMeta(application.value) + toaster.general.success({ + text: 'Application successfully updated', + title: 'Success' + }) + reset() + editMode.value = false + }) +} + +const updateSecret = async function () { + // Show a confrimation prompt + const { isCanceled } = await reveal({ + title: 'Update Secret', + text: `Are you sure you want to update the secret? Any active sessions will be invalidated, and the old secret will be invalidated.` + }) + if (isCanceled) { + return + } + await elevatedApiCall(async ({ password }) => { + // Submit the secret update with the new challenge + newSecret.value = await updateAppSecret(application.value, password) + }) +} + +const onDelete = async function () { + // Show a confirmation prompt + const { isCanceled } = await reveal({ + title: 'Delete Application', + text: 'Are you sure you want to delete this application?', + subtext: 'This action cannot be undone' + }) + if (isCanceled) { + return + } + await elevatedApiCall(async ({ password, toaster }) => { + await deleteApp(application.value, password) + toaster.general.success({ + text: 'Application deleted successfully', + title: 'Success' + }) + emit('AppDeleted') + }) +} + +const closeNewSecret = () => newSecret.value = null; + +</script> + +<style lang="scss"> + + +</style> diff --git a/front-end/src/views/Account/components/oauth/o2Api.ts b/front-end/src/views/Account/components/oauth/o2Api.ts new file mode 100644 index 0000000..c21e4ed --- /dev/null +++ b/front-end/src/views/Account/components/oauth/o2Api.ts @@ -0,0 +1,176 @@ +import { forEach } from 'lodash' +import { Ref } from 'vue' +import useVuelidate from '@vuelidate/core' +import { maxLength, helpers, required } from '@vuelidate/validators' +import { useAxios, useDataBuffer, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { AxiosResponse } from 'axios' + +export interface OAuth2Application{ + readonly Id: string, + readonly name: string, + readonly description: string, + readonly permissions: string[], + readonly client_id: string, + readonly Created: Date, + readonly LastModified: Date, +} + +export interface NewAppResponse { + readonly secret: string + readonly app: AppBuffer<OAuth2Application> +} + +export interface AppBuffer<T>{ + readonly data: T, + buffer: T + readonly modified: Readonly<Ref<boolean>> + apply: (data: T) => void + revert(): void +} + +/** + * Initializes the oauth2 applications api + * @param o2EndpointUrl The url of the oauth2 applications endpoint + * @returns The oauth2 applications api + */ +export const useOAuth2Apps = (o2EndpointUrl : string) => { + const { post, get, put } = useAxios(null); + + /** + * Gets all of the user's oauth2 applications from the server + * @returns The user's oauth2 applications + */ + const getApps = async (): Promise<AppBuffer<OAuth2Application>[]>=> { + // Get all apps + const { data } = await get(o2EndpointUrl); + + const apps: AppBuffer<OAuth2Application>[] = [] + + //Loop through the apps and create a new state manager for each + forEach(data, (appData) => { + + //Store the created time as a date object + appData.created = new Date(appData?.Created ?? 0) + + //create a new state manager for the user's profile + const app: AppBuffer<OAuth2Application> = useDataBuffer(appData) + + apps.push(app) + }) + + return apps + } + + /** + * Creates a new application from the given data + * @param param0 The application server buffer + * @returns The newly created application + */ + const createApp = async ({ name, description, permissions } : OAuth2Application): Promise<NewAppResponse> => { + + // make the post request, response is the new app data with a secret + const { data } = await post(`${o2EndpointUrl}?action=create`, { name, description, permissions }) + + // Store secret + const secret = data.raw_secret + + // remove secre tfrom the response + delete data.raw_secret + + return { secret, app: useDataBuffer(data) } + } + + /** + * Updates an Oauth2 application's metadata + */ + const updateAppMeta = async (app: AppBuffer<OAuth2Application>): Promise<void> => { + + //Update the app metadata + await put(o2EndpointUrl, app.buffer) + + //Get the app data from the server to update the local copy + const response = await get(`${o2EndpointUrl}?Id=${app.data.Id}`) + + //Update the app + app.apply(response.data) + } + + /** + * Requets a new secret for an application from the server + * @param app The app to request a new secret for + * @param password The user's password + * @returns The new secret + */ + const updateAppSecret = async (app: AppBuffer<OAuth2Application>, password: string): Promise<string> => { + const response = await post(`${o2EndpointUrl}?action=secret`, { Id: app.data.Id, password }) + return response.data.raw_secret + } + + /** + * Deletes an application from the server + * @param app The application to delete + * @param password The user's password + * @returns The response from the server + */ + const deleteApp = (app: AppBuffer<OAuth2Application>, password: string): Promise<AxiosResponse> => { + return post(`${o2EndpointUrl}?action=delete`, { password, Id: app.data.Id }); + } + + return { getApps, createApp, updateAppMeta, updateAppSecret, deleteApp } +} + + +//Custom alpha numeric regex +const alphaNumRegex = helpers.regex(/^[a-zA-Z0-9_,\s]*$/) + +const rules = { + name: { + alphaNumSpace: helpers.withMessage("Name contains invalid characters", alphaNumRegex), + maxLength: helpers.withMessage('App name must be less than 50 characters', maxLength(50)), + required: helpers.withMessage('Oauth Application name is required', required) + }, + description: { + alphaNumSpace: helpers.withMessage("Description contains invalid characters", alphaNumRegex), + maxLength: helpers.withMessage('Description must be less than 50 characters', maxLength(50)) + }, + permissions: { + alphaNumSpace: helpers.regex(/^[a-zA-Z0-9_,:\s]*$/), + maxLength: helpers.withMessage('Permissions must be less than 64 characters', maxLength(64)) + } +} + +export interface AppValidator { + readonly v$: ReturnType<typeof useVuelidate> + readonly validate: () => Promise<boolean> + readonly reset: () => void +} + +/** + * Gets the validator for the given application (or new appication) buffer + * @param buffer The app buffer to validate + * @returns The validator instance, validate function, and reset function + */ +export const getAppValidator = <T>(buffer: T) : AppValidator => { + //App validator + const v$ = useVuelidate(rules, buffer, { $lazy: true, $autoDirty: true }) + //validate wrapper function + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; +} + +export const getAppPermissions = () =>{ + return [ + { + type: 'account:read', + label: 'Account Read' + }, + { + type: 'account:write', + label: 'Account Write' + }, + { + type: 'email:send', + label: 'Send Emails' + } + ] +}
\ No newline at end of file diff --git a/front-end/src/views/Account/components/profile/Profile.vue b/front-end/src/views/Account/components/profile/Profile.vue new file mode 100644 index 0000000..e01707c --- /dev/null +++ b/front-end/src/views/Account/components/profile/Profile.vue @@ -0,0 +1,199 @@ +<template> + <div id="account-profile" class="acnt-content-container panel-container"> + <div class="acnt-content profile-container panel-content"> + + <div id="profile-control-container" class="flex flex-row" :modified="modified"> + <div class="m-0"> + <div class="flex rounded-full w-14 h-14 bg-primary-500 dark:bg-primary-600"> + <div class="m-auto text-white dark:text-dark-400"> + <fa-icon :icon="['fas', 'user']" size="2xl" /> + </div> + </div> + </div> + + <div class="my-auto ml-6"> + <h3 class="m-0">Profile</h3> + </div> + + <div class="gap-3 ml-auto"> + <div v-if="editMode" class="button-group"> + <button form="profile-edit-form" class="btn primary sm" :disabled="waiting" @click="onSubmit">Submit</button> + <button class="btn sm" @click="revertProfile">Cancel</button> + </div> + <div v-else class=""> + <button class="btn no-border" @click="editMode = true">Edit</button> + </div> + </div> + </div> + + <div> + + <p class="profile-text"> + You may set or change your profile information here. All fields are optional, + but some features may not work without some information. + </p> + + <div class="locked-info"> + <div class="mx-auto my-1 sm:mx-0 sm:my-2"> + <span class="pr-2">Email:</span> + <span class="">{{ data.email }}</span> + </div> + <div class="mx-auto my-1 sm:mx-0 sm:my-2"> + <span class="pr-2">Created:</span> + <span>{{ createdTime }}</span> + </div> + </div> + + <dynamic-form id="profile-edit-form" + :form="FormSchema" + :disabled="!editMode" + :validator="v$" + @submit="onSubmit" + @input="onInput" + /> + </div> + + </div> + </div> +</template> + +<script setup lang="ts"> +import { defaultTo } from 'lodash' +import useVuelidate from '@vuelidate/core' +import { ref, computed, watch } from 'vue' +import { Rules, FormSchema } from './profile-schema.ts' +import { apiCall, useMessage, useWait, useDataBuffer, useUser, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { IUserProfile } from '@vnuge/vnlib.browser/dist/user' + +const ACCOUNT_URL = '/account/profile' + +interface UserProfile extends IUserProfile{ + created : string | Date +} + +const { waiting } = useWait() +const { getProfile } = useUser() +const { onInput, clearMessage } = useMessage() +const { data, buffer, apply, revert, modified } = useDataBuffer<UserProfile>({} as UserProfile) + +const editMode = ref(false) + +// Create validator based on the profile buffer as a data model +const v$ = useVuelidate(Rules, buffer, { $lazy:true, $autoDirty:false }) + +// Setup the validator wrapper +const { validate } = useVuelidateWrapper(v$); + +//const modified = computed(() => profile.value.Modified) +const createdTime = computed(() => defaultTo(data.created?.toLocaleString(), '')) + +const loadProfileData = async () => { + await apiCall(async () => { + // Get the user's profile + const profile = await getProfile<UserProfile>() + profile.created = new Date(profile.created) + //Apply the profile to the buffer + apply(profile) + }) +} + +const revertProfile = () => { + //Revert the buffer + revert() + clearMessage() + editMode.value = false +} + +const onSubmit = async () => { + if(waiting.value){ + return; + } + // Validate the form + if (!await validate()) { + return + } + // Init the api call + await apiCall(async ({ axios, toaster }) => { + // Apply the buffer to the profile + const response = await axios.post(ACCOUNT_URL, buffer) + + if(!response.data.success){ + throw { response } + } + + //No longer in edit mode + editMode.value = false + + //Show success message + toaster.general.success({ + title: 'Update successful', + text: response.data.result, + }) + + //reload the profile data + loadProfileData() + }) +} + +watch(editMode, () => v$.value.$reset()) + +//Inital profile data load, dont await +loadProfileData() + +</script> + +<style lang="scss"> + +#account-profile { + + p.profile-text{ + @apply p-2 md:py-3 md:my-1 text-sm; + } + + .locked-info{ + @apply w-full flex flex-col sm:flex-row sm:justify-evenly pt-3 sm:pb-1; + } + + #profile-edit-form .input-group { + @apply pt-4; + + .input-container{ + @apply p-2 rounded-md flex sm:flex-row flex-col sm:gap-4; + + &.dirty.data-invalid.dynamic-form .dynamic-input{ + @apply border-red-600; + } + + &.dirty.data-invalid.dynamic-form label.dynamic-form{ + @apply text-red-500; + } + + &.dirty.dynamic-form label.dynamic-form{ + @apply text-primary-500; + } + + select:disabled{ + @apply appearance-none; + } + } + + .input-container:nth-child(odd) { + @apply bg-slate-50 dark:bg-dark-700; + } + + .dynamic-form.dynamic-input{ + @apply py-2 w-full bg-transparent border-x-0 border-t-0 border-b border-gray-300 dark:border-dark-300 pl-2; + @apply focus:bg-gray-200 focus:dark:bg-transparent; + + &:disabled{ + @apply py-1 border-transparent; + } + } + + label.dynamic-form{ + flex-basis: 15%; + @apply sm:text-right my-auto; + } + } +} +</style> diff --git a/front-end/src/views/Account/components/profile/profile-schema.ts b/front-end/src/views/Account/components/profile/profile-schema.ts new file mode 100644 index 0000000..85dacff --- /dev/null +++ b/front-end/src/views/Account/components/profile/profile-schema.ts @@ -0,0 +1,310 @@ + +import { maxLength, helpers, numeric, alpha, alphaNum } from '@vuelidate/validators' + +export const Rules = { + first: { + alpha, + maxLength: helpers.withMessage('First name must be less than 50 characters', maxLength(50)) + }, + last: { + alpha, + maxLength: helpers.withMessage('Last name must be less than 50 characters', maxLength(50)) + }, + company: { + alphaNum: helpers.regex(/^[a-zA-Z0-9\s.&!]*$/), + maxLength: helpers.withMessage('Company name must be less than 50 characters', maxLength(50)) + }, + phone: { + numeric: helpers.withMessage('Phone number must contain only numbers', numeric), + maxLength: helpers.withMessage('Phone number must be less than 11 numbers', maxLength(11)) + }, + street: { + alphaNum: helpers.regex(/^[a-zA-Z0-9\s&]*$/), + maxLength: helpers.withMessage('Street name must be less than 50 characters', maxLength(50)) + }, + city: { + alphaNum, + maxLength: helpers.withMessage('City name must be less than 50 characters', maxLength(50)) + }, + state: { + alpha, + maxLength: helpers.withMessage('State code is invalid', maxLength(2)) + }, + zip: { + numeric, + maxLength: helpers.withMessage('Zip code must be exactly 5 numbers', maxLength(5)) + } +} + + +export const FormSchema = { + id: 'profile-edit-form', + fields: [ + { + label: 'First', + name: 'first', + type: 'text', + id: 'first-name' + }, + { + label: 'Last', + name: 'last', + type: 'text', + id: 'last-name' + }, + { + label: 'Company', + name: 'company', + type: 'text', + id: 'company' + }, + { + label: 'Phone', + name: 'phone', + type: 'text', + id: 'phone' + }, + { + label: 'Street', + name: 'street', + type: 'text', + id: 'street' + }, + { + label: 'City', + name: 'city', + type: 'text', + id: 'city' + }, + { + label: 'State', + name: 'state', + type: 'select', + id: 'state', + options: [ + { + 'label':'Select State', + 'value': '' + }, + { + "label": "Alabama", + "value": "AL" + }, + { + "label": "Alaska", + "value": "AK" + }, + { + "label": "Arizona", + "value": "AZ" + }, + { + "label": "Arkansas", + "value": "AR" + }, + { + "label": "California", + "value": "CA" + }, + { + "label": "Colorado", + "value": "CO" + }, + { + "label": "Connecticut", + "value": "CT" + }, + { + "label": "Delaware", + "value": "DE" + }, + { + "label": "District Of Columbia", + "value": "DC" + }, + { + "label": "Florida", + "value": "FL" + }, + { + "label": "Georgia", + "value": "GA" + }, + { + "label": "Guam", + "value": "GU" + }, + { + "label": "Hawaii", + "value": "HI" + }, + { + "label": "Idaho", + "value": "ID" + }, + { + "label": "Illinois", + "value": "IL" + }, + { + "label": "Indiana", + "value": "IN" + }, + { + "label": "Iowa", + "value": "IA" + }, + { + "label": "Kansas", + "value": "KS" + }, + { + "label": "Kentucky", + "value": "KY" + }, + { + "label": "Louisiana", + "value": "LA" + }, + { + "label": "Maine", + "value": "ME" + }, + { + "label": "Maryland", + "value": "MD" + }, + { + "label": "Massachusetts", + "value": "MA" + }, + { + "label": "Michigan", + "value": "MI" + }, + { + "label": "Minnesota", + "value": "MN" + }, + { + "label": "Mississippi", + "value": "MS" + }, + { + "label": "Missouri", + "value": "MO" + }, + { + "label": "Montana", + "value": "MT" + }, + { + "label": "Nebraska", + "value": "NE" + }, + { + "label": "Nevada", + "value": "NV" + }, + { + "label": "New Hampshire", + "value": "NH" + }, + { + "label": "New Jersey", + "value": "NJ" + }, + { + "label": "New Mexico", + "value": "NM" + }, + { + "label": "New York", + "value": "NY" + }, + { + "label": "North Carolina", + "value": "NC" + }, + { + "label": "North Dakota", + "value": "ND" + }, + { + "label": "Ohio", + "value": "OH" + }, + { + "label": "Oklahoma", + "value": "OK" + }, + { + "label": "Oregon", + "value": "OR" + }, + { + "label": "Pennsylvania", + "value": "PA" + }, + { + "label": "Puerto Rico", + "value": "PR" + }, + { + "label": "Rhode Island", + "value": "RI" + }, + { + "label": "South Carolina", + "value": "SC" + }, + { + "label": "South Dakota", + "value": "SD" + }, + { + "label": "Tennessee", + "value": "TN" + }, + { + "label": "Texas", + "value": "TX" + }, + { + "label": "Utah", + "value": "UT" + }, + { + "label": "Vermont", + "value": "VT" + }, + { + "label": "Virginia", + "value": "VA" + }, + { + "label": "Washington", + "value": "WA" + }, + { + "label": "West Virginia", + "value": "WV" + }, + { + "label": "Wisconsin", + "value": "WI" + }, + { + "label": "Wyoming", + "value": "WY" + } + ] + }, + { + label: 'Zip', + name: 'zip', + type: 'text', + id: 'zip' + } + ] +}
\ No newline at end of file diff --git a/front-end/src/views/Account/components/settings/Fido.vue b/front-end/src/views/Account/components/settings/Fido.vue new file mode 100644 index 0000000..340d6d9 --- /dev/null +++ b/front-end/src/views/Account/components/settings/Fido.vue @@ -0,0 +1,53 @@ +<template> + <div id="account-fido-settings"> + <div v-if="!isLocalAccount" class="flex flex-row justify-between"> + <h6 class="block"> + FIDO/WebAuthN Authentication + </h6> + <div class="text-red-500"> + Unavailable for external auth + </div> + </div> + <div v-else class="flex flex-row flex-wrap justify-between"> + <h6>FIDO/WebAuthN Authentication</h6> + <div class=""> + <div v-if="fidoEnabled" class=""> + <button class="ml-1 btn red sm" @click.prevent="Disable"> + <fa-icon icon="minus-circle" /> + <span class="pl-3">Disable</span> + </button> + </div> + <div v-else> + <button class="btn primary sm" @click.prevent="Setup"> + <fa-icon icon="plus" /> + <span class="pl-3">Setup</span> + </button> + </div> + </div> + <p class="p-1 pt-3 text-sm text-gray-600"> + WebAuthN/FIDO is not yet supported, due to complexity and browser support. + </p> + </div> + </div> +</template> + +<script setup lang="ts"> +import { useSession } from '@vnuge/vnlib.browser' +import { toRefs } from 'vue'; + +const props = defineProps<{ + fidoEnabled?: boolean +}>() + +const { fidoEnabled } = toRefs(props) + +const { isLocalAccount } = useSession() + +const Disable = () => {} +const Setup = () => {} + +</script> + +<style> + +</style> diff --git a/front-end/src/views/Account/components/settings/PasswordReset.vue b/front-end/src/views/Account/components/settings/PasswordReset.vue new file mode 100644 index 0000000..ff04193 --- /dev/null +++ b/front-end/src/views/Account/components/settings/PasswordReset.vue @@ -0,0 +1,235 @@ +<template> + <div id="pwreset-settings" class="container"> + <div class="panel-content"> + + <h5>Password Reset</h5> + + <div v-if="!pwResetShow" class="py-2"> + <div class="flex flex-wrap items-center justify-between"> + + <div class="my-auto"> + Click to reset + </div> + + <div class="flex justify-end"> + <button class="btn red sm" @click="showForm"> + <fa-icon icon="sync" /> + <span class="pl-3">Reset Password</span> + </button> + </div> + </div> + + <p class="mt-3 text-sm"> + You may only reset your password if you have an internal user account. If you exclusivly use an external + authentication provider (like GitHub or Discord), you will need to reset your password externally. + </p> + </div> + + <div v-else class="px-2 my-2"> + + <p class="my-3 text-center"> + Enter your current password, new password, and confirm the new password. + </p> + + <dynamic-form + id="password-reset-form" + class="pwreset-form primary" + :form="formSchema" + :disabled="waiting" + :validator="v$" + @submit="onSubmit" + @input="onInput" + /> + + <div class="flex flex-row justify-end my-2"> + <div class="button-group"> + <button type="submit" form="password-reset-form" class="btn primary sm" :disabled="waiting"> + <fa-icon v-if="!waiting" icon="check" /> + <fa-icon v-else class="animate-spin" icon="spinner" /> + Update + </button> + <button class="btn sm cancel-btn" :disabled="waiting" @click="resetForm"> + Cancel + </button> + </div> + </div> + + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { toSafeInteger } from 'lodash'; +import useVuelidate from '@vuelidate/core' +import { required, maxLength, minLength, helpers } from '@vuelidate/validators' +import { useUser, apiCall, useMessage, useWait, useConfirm, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import { computed, reactive, ref, toRefs, watch } from 'vue' + +const props = defineProps<{ + totpEnabled: boolean, + fidoEnabled: boolean +}>() + +const { totpEnabled, fidoEnabled } = toRefs(props) + +const formSchema = ref({ + fields: [ + { + label: 'Current Password', + name: 'current', + type: 'password', + id: 'current-password' + }, + { + label: 'New Password', + name: 'newPassword', + type: 'password', + id: 'new-password' + }, + { + label: 'Confirm Password', + name: 'repeatPassword', + type: 'password', + id: 'confirm-password' + } + ] +}) + +const { waiting } = useWait() +const { onInput } = useMessage() +const { reveal } = useConfirm() +const { resetPassword } = useUser() + +const pwResetShow = ref(false) + +const vState = reactive({ + newPassword: '', + repeatPassword: '', + current: '', + totpCode:'' +}) + +const rules = computed(() =>{ + return { + current: { + required: helpers.withMessage('Current password cannot be empty', required), + minLength: helpers.withMessage('Current password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('Current password must have less than 128 characters', maxLength(128)) + }, + newPassword: { + notOld: helpers.withMessage('New password cannot be the same as your current password', (value : string) => value != vState.current), + required: helpers.withMessage('New password cannot be empty', required), + minLength: helpers.withMessage('New password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('New password must have less than 128 characters', maxLength(128)) + }, + repeatPassword: { + sameAs: helpers.withMessage('Your new passwords do not match', (value : string) => value == vState.newPassword), + required: helpers.withMessage('Repeast password cannot be empty', required), + minLength: helpers.withMessage('Repeast password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('Repeast password must have less than 128 characters', maxLength(128)) + }, + totpCode:{ + required: helpers.withMessage('TOTP code cannot be empty', required), + minLength: helpers.withMessage('TOTP code must be at least 6 characters', minLength(6)), + maxLength: helpers.withMessage('TOTP code must have less than 12 characters', maxLength(12)) + } + } +}) + +const v$ = useVuelidate(rules, vState, { $lazy: true }) +const { validate } = useVuelidateWrapper(v$) + +const showTotpCode = computed(() => totpEnabled.value && !fidoEnabled.value) + +watch(showTotpCode, (val) => { + if(val){ + //Add totp code field + formSchema.value.fields.push({ + label: 'TOTP Code', + name: 'totpCode', + type: 'text', + id: 'totp-code' + }) + } +}) + +const showForm = async function () { + const { isCanceled } = await reveal({ + title: 'Reset Password', + text: 'Are you sure you want to reset your password? This cannot be reversed.' + }) + pwResetShow.value = !isCanceled +} + +const onSubmit = async () => { + + // validate + if (! await validate()) { + return + } + + interface IResetPasswordArgs { + totp_code?: number + } + + await apiCall(async ({ toaster }) => { + const args : IResetPasswordArgs = {} + + //Add totp code if enabled + if(showTotpCode.value){ + args.totp_code = toSafeInteger(v$.value.totpCode.$model) + } + + //Exec pw reset + const { getResultOrThrow } = await resetPassword(v$.value.current.$model, v$.value.newPassword.$model, args) + + //Get result or raise exception to handler + const result = getResultOrThrow() + + // success + resetForm() + + // Push a success toast + toaster.general.success({ + title: 'Success', + text: result + }) + + }) +} + +const resetForm = () => { + v$.value.current.$model = '' + v$.value.newPassword.$model = '' + v$.value.repeatPassword.$model = '' + v$.value.totpCode.$model = '' + v$.value.$reset() + pwResetShow.value = false +} + +</script> + +<style lang="scss"> + +#password-reset-form{ + + .dynamic-form.input-container{ + @apply flex flex-col sm:flex-row my-4 max-w-lg mx-auto; + + label{ + flex-basis: 40%; + @apply pl-1 text-sm sm:text-right my-auto mr-2 mb-1 sm:mb-auto; + } + } + + .dynamic-form.dynamic-input.input { + @apply p-2 w-full border rounded-md; + @apply focus:border-primary-500 focus:dark:border-primary-600 dark:border-dark-400 bg-transparent dark:bg-dark-800; + } + .dirty.data-invalid.dynamic-form.input-container input{ + @apply border-red-500 focus:border-red-500; + } +} + +</style> diff --git a/front-end/src/views/Account/components/settings/Pki.vue b/front-end/src/views/Account/components/settings/Pki.vue new file mode 100644 index 0000000..a621bf2 --- /dev/null +++ b/front-end/src/views/Account/components/settings/Pki.vue @@ -0,0 +1,182 @@ +<template> + <div id="pki-settings" v-show="pkiEnabled" class="container"> + <div class="panel-content"> + <h5>PKI Authentication</h5> + <div class="flex flex-row flex-wrap justify-between"> + <h6>Authentication keys</h6> + + <div v-if="enabled" class="button-group"> + <button class="btn yellow sm" @click.prevent="setIsOpen(true)"> + <fa-icon icon="sync" /> + <span class="pl-3">Update Key</span> + </button> + <button class="btn red sm" @click.prevent="onDisable"> + <fa-icon icon="minus-circle" /> + <span class="pl-3">Disable</span> + </button> + </div> + + <div v-else class=""> + <button class="btn primary sm" @click.prevent="setIsOpen(true)"> + <fa-icon icon="plus" /> + <span class="pl-3">Add Key</span> + </button> + </div> + + <p class="p-1 pt-3 text-sm text-gray-600"> + PKI authentication is a method of authenticating your user account with signed messages and a shared public key. This method implementation + uses client signed Json Web Tokens to authenticate user generated outside this website as a One Time Password (OTP). This allows for you to + use your favorite hardware or software tools, to generate said OTPs to authenticate your user. + + </p> + </div> + </div> + </div> + <Dialog :open="isOpen" @close="setIsOpen" class="relative z-30"> + <div class="fixed inset-0 bg-black/30" aria-hidden="true" /> + + <div class="fixed inset-0 flex justify-center"> + <DialogPanel class="w-full max-w-lg p-4 m-auto mt-24 bg-white rounded dark:bg-dark-600 dark:text-gray-300"> + <h4>Configure your authentication key</h4> + <p class="mt-2 text-sm"> + Please paste your authenticator's public key as a Json Web Key (JWK) object. Your JWK must include a kid (key id) and a kty (key type) field. + </p> + <div class="p-2 mt-3"> + <textarea class="w-full p-1 text-sm border dark:bg-dark-700 ring-0 dark:border-dark-400" rows="10" v-model="keyData" /> + </div> + <div class="flex justify-end gap-2 mt-4"> + <button class="rounded btn sm primary" @click.prevent="onSubmitKeys">Submit</button> + <button class="rounded btn sm red" @click.prevent="setIsOpen(false)">Cancel</button> + </div> + </DialogPanel> + </div> + </Dialog> +</template> + +<script setup lang="ts"> +import { isEmpty, isNil } from 'lodash' +import { apiCall, useConfirm, useSession, debugLog, useFormToaster } from '@vnuge/vnlib.browser' +import { computed, ref, watch } from 'vue' +import { Dialog, DialogPanel } from '@headlessui/vue' +import { PkiApi } from '@vnuge/vnlib.browser/dist/mfa'; + +const props = defineProps<{ + pkaiApi: PkiApi +}>() + +const { reveal } = useConfirm() +const { isLocalAccount } = useSession() +const { error } = useFormToaster() + +const pkiEnabled = computed(() => isLocalAccount.value && !isNil(import.meta.env.VITE_PKI_ENDPOINT) && window.crypto.subtle) +const { enabled } = props.pkaiApi + +const isOpen = ref(false) +const keyData = ref('') +const pemFormat = ref(false) +const explicitCurve = ref("") + +watch(isOpen, () =>{ + keyData.value = '' + pemFormat.value = false + explicitCurve.value = "" + //Reload status + props.pkaiApi.refresh() +}) + +const setIsOpen = (value : boolean) => isOpen.value = value + +const onDisable = async () => { + const { isCanceled } = await reveal({ + title: 'Are you sure?', + text: 'This will disable PKI authentication for your account.' + }) + if (isCanceled) { + return; + } + + //Delete pki + await apiCall(async ({ toaster }) =>{ + + //Disable pki + //TODO: require password or some upgrade to disable + const { success } = await props.pkaiApi.disable(); + + if(success){ + toaster.general.success({ + title: 'Success', + text: 'PKI authentication has been disabled.' + }) + } + else{ + toaster.general.error({ + title: 'Error', + text: 'PKI authentication could not be disabled.' + }) + } + + //Refresh the status + props.pkaiApi.refresh(); + }); +} + +//Server requires the JWK to set a keyid (kid) field +interface IdJsonWebKey extends JsonWebKey { + readonly kid?: string +} + +const onSubmitKeys = async () =>{ + + if(window.crypto.subtle == null){ + error({ title: "Your browser does not support PKI authentication." }) + return; + } + + //Validate key data + if(isEmpty(keyData.value)){ + error({ title: "Please enter key data" }) + return; + } + + let jwk : IdJsonWebKey; + try { + //Try to parse as jwk + jwk = JSON.parse(keyData.value) + if(isEmpty(jwk.use) + || isEmpty(jwk.kty) + || isEmpty(jwk.alg) + || isEmpty(jwk.kid) + || isEmpty(jwk.x) + || isEmpty(jwk.y)){ + throw new Error("Invalid JWK"); + } + } + catch (e) { + //Write error to debug log + debugLog(e) + error({ title:"The key is not a valid Json Web Key (JWK)"}) + return; + } + + //Send to server + await apiCall(async ({ toaster }) => { + + //init/update the key + //TODO: require password or some upgrade to disable + const { getResultOrThrow } = await props.pkaiApi.initOrUpdate(jwk); + + const result = getResultOrThrow(); + + toaster.general.success({ + title: 'Success', + text: result + }) + setIsOpen(false) + }) +} + +</script> + +<style> + +</style> diff --git a/front-end/src/views/Account/components/settings/Security.vue b/front-end/src/views/Account/components/settings/Security.vue new file mode 100644 index 0000000..9ba83f7 --- /dev/null +++ b/front-end/src/views/Account/components/settings/Security.vue @@ -0,0 +1,81 @@ +<template> + <div id="account-security-settings"> + <div class="panel-container"> + + <div class="panel-header"> + <div class="panel-title"> + <h4>Security</h4> + </div> + </div> + + <password-reset :totpEnabled="totpEnabled" :fido-enabled="fidoEnabled" /> + + <div id="account-mfa-settings" class="panel-content"> + <h5>Multi Factor Authentication</h5> + <div class="py-2 border-b-2 border-gray-200 dark:border-dark-400"> + <TotpSettings :mfa="mfaApi" /> + </div> + <div class="py-2"> + <Fido :fido-enabled="fidoEnabled"/> + </div> + </div> + + <Pki :pkai-api="pkiApi" /> + + <div id="browser-poll-settings" class="panel-content" > + <div class="flex justify-between"> + <h5>Keep me logged in</h5> + <div class="pl-1"> + <Switch + v-model="enabled" + :class="enabled ? 'bg-primary-500 dark:bg-primary-600' : 'bg-gray-200 dark:bg-dark-400'" + class="relative inline-flex items-center h-6 rounded-full w-11" + > + <span class="sr-only">Enable auto heartbeat</span> + <span + :class="enabled ? 'translate-x-6' : 'translate-x-1'" + class="inline-block w-4 h-4 transition transform bg-white rounded-full" + /> + </Switch> + </div> + </div> + + <p class="p-1 text-sm"> + When enabled, continuously regenerates your login credentials to keep you logged in. The longer you are logged in, + the easier session fixation attacks become. If disabled, you will need to log when your credentials have expired. + It is recommneded that you leave this disabled <span class="text-yellow-500">Disabled</span> + </p> + + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { useAutoHeartbeat } from '@vnuge/vnlib.browser' +import { useMfaConfig, MfaMethod, usePkiConfig } from '@vnuge/vnlib.browser/dist/mfa' +import { computed } from 'vue' +import { Switch } from '@headlessui/vue' +import { includes } from 'lodash' +import Fido from './Fido.vue' +import Pki from './Pki.vue' +import TotpSettings from './TotpSettings.vue' +import PasswordReset from './PasswordReset.vue' + +const { enabled } = useAutoHeartbeat() + +const mfaApi = useMfaConfig('/account/mfa') +const pkiApi = usePkiConfig(import.meta.env.VITE_PKI_ENDPOINT, mfaApi) + +const fidoEnabled = computed(() => includes(mfaApi.enabledMethods.value, 'fido' as MfaMethod)) +const totpEnabled = computed(() => includes(mfaApi.enabledMethods.value, MfaMethod.TOTP)) + +</script> + +<style> + +#account-security-settings .modal-body{ + @apply w-full sm:max-w-md ; +} + +</style> diff --git a/front-end/src/views/Account/components/settings/Settings.vue b/front-end/src/views/Account/components/settings/Settings.vue new file mode 100644 index 0000000..cd2ab48 --- /dev/null +++ b/front-end/src/views/Account/components/settings/Settings.vue @@ -0,0 +1,16 @@ +<template> + <div id="account-settings" class="container"> + <div class="acnt-content-container"> + <Security /> + </div> + </div> +</template> + +<script setup lang="ts"> +import Security from './security.vue' + +</script> + +<style> + +</style> diff --git a/front-end/src/views/Account/components/settings/TotpSettings.vue b/front-end/src/views/Account/components/settings/TotpSettings.vue new file mode 100644 index 0000000..20ee0d0 --- /dev/null +++ b/front-end/src/views/Account/components/settings/TotpSettings.vue @@ -0,0 +1,263 @@ +<template> + <div id="totp-settings"> + + <div v-if="!isLocalAccount" class="flex flex-row justify-between"> + <h6 class="block"> + TOTP Authenticator App + </h6> + <div class="text-red-500"> + Unavailable for external auth + </div> + </div> + + <div v-else-if="showTotpCode" class="w-full py-2 text-center"> + <h5 class="text-center" /> + <p class="py-2"> + Scan the QR code with your TOTP authenticator app. + </p> + + <div class="flex"> + <VueQrcode class="m-auto" :value="qrCode" /> + </div> + + <p class="py-2"> + Your secret, if your application requires it. + </p> + + <p class="flex flex-row flex-wrap justify-center p-2 bg-gray-200 border border-gray-300 dark:bg-dark-800 dark:border-dark-300"> + <span v-for="code in secretSegments" :key="code" class="px-2 font-mono tracking-wider" > + {{ code }} + </span> + </p> + + <p class="py-2"> + Please enter your code from your authenticator app to continue. + </p> + + <div class="m-auto w-min"> + <VOtpInput + class="otp-input" + input-type="letter-numeric" + separator="" + :is-disabled="showSubmitButton" + input-classes="primary input rounded" + :num-inputs="6" + @on-change="onInput" + @on-complete="VerifyTotp" + /> + </div> + + <div v-if="showSubmitButton" class="flex flex-row justify-end my-2"> + <button class="btn primary" @click.prevent="CloseQrWindow"> + Complete + </button> + </div> + </div> + + <div v-else class="flex flex-row flex-wrap justify-between"> + <h6>TOTP Authenticator App</h6> + + <div v-if="totpEnabled" class="button-group"> + <button class="btn yellow sm" @click.prevent="regenTotp"> + <fa-icon icon="sync" /> + <span class="pl-3">Regenerate</span> + </button> + <button class="btn red sm" @click.prevent="disable"> + <fa-icon icon="minus-circle" /> + <span class="pl-3">Disable</span> + </button> + </div> + + <div v-else> + <button class="btn primary sm" @click.prevent="configTotp"> + <fa-icon icon="plus" /> + <span class="pl-3">Setup</span> + </button> + </div> + <p class="p-1 pt-3 text-sm text-gray-600"> + TOTP is a time based one time password. You can use it as a form of Multi Factor Authentication when + using another device such as a smart phone or TOTP hardware device. You can use TOTP with your smart + phone + using apps like Google Authenticator, Authy, or Duo. Read more on + <a class="link" href="https://en.wikipedia.org/wiki/Time-based_one-time_password" target="_blank"> + Wikipedia. + </a> + </p> + </div> + + </div> +</template> + +<script setup lang="ts"> +import { isNil, chunk, defaultTo, includes, map, join } from 'lodash' +import { TOTP } from 'otpauth' +import base32Encode from 'base32-encode' +import VueQrcode from '@chenfengyuan/vue-qrcode' +import VOtpInput from "vue3-otp-input"; +import { computed, ref } from 'vue' +import { + useSessionUtils, + useSession, + useUser, + useMessage, + useConfirm, + usePassConfirm, + useFormToaster +} from '@vnuge/vnlib.browser' +import { MfaApi, MfaMethod } from '@vnuge/vnlib.browser/dist/mfa'; + +interface TotpConfig{ + secret: string; + readonly issuer: string; + readonly algorithm: string; + readonly digits?: number; + readonly period?: number; +} + +const props = defineProps<{ + mfa: MfaApi +}>() + +const { isLocalAccount } = useSession() +const { KeyStore } = useSessionUtils() +const { userName } = useUser() +const { reveal } = useConfirm() +const { elevatedApiCall } = usePassConfirm() +const { onInput, setMessage } = useMessage() + +const { enabledMethods, disableMethod, initOrUpdateMethod, refreshMethods } = props.mfa; +const totpEnabled = computed(() => includes(enabledMethods.value, MfaMethod.TOTP)) + +const totpMessage = ref<TotpConfig>() +const showSubmitButton = ref(false) +const toaster = useFormToaster() + +const showTotpCode = computed(() => !isNil(totpMessage.value?.secret)) + +const secretSegments = computed<string[]>(() => { + //Chunk the secret into 6 character segments + const chunks = chunk(totpMessage.value?.secret, 6) + //Join the chunks into their chunk arrays + return map(chunks, chunk => join(chunk, '')) +}) + +const qrCode = computed(() => { + if (isNil(totpMessage.value?.secret)) { + return '' + } + + const m = totpMessage.value!; + + // Build the totp qr codeurl + const params = new URLSearchParams() + params.append('secret', m.secret) + params.append('issuer', m.issuer) + params.append('algorithm', m.algorithm) + params.append('digits', defaultTo(m.digits, 6).toString()) + params.append('period', defaultTo(m.period, 30).toString()) + const url = `otpauth://totp/${m.issuer}:${userName.value}?${params.toString()}` + return url +}) + +const ProcessAddOrUpdate = async () => { + await elevatedApiCall(async ({ password }) => { + + // Init or update the totp method and get the encrypted totp message + const res = await initOrUpdateMethod<TotpConfig>(MfaMethod.TOTP, password); + + //Get the encrypted totp message + const totp = res.getResultOrThrow() + + // Decrypt the totp secret + const secretBuf = await KeyStore.decryptDataAsync(totp.secret) + + // Encode the secret to base32 + totp.secret = base32Encode(secretBuf, 'RFC3548', { padding: false }) + + totpMessage.value = totp + }) +} + +const configTotp = async () => { + const { isCanceled } = await reveal({ + title: 'Enable TOTP multi factor?', + text: 'Are you sure you understand TOTP multi factor and wish to enable it?', + }) + + if(!isCanceled){ + ProcessAddOrUpdate() + } +} + +const regenTotp = async () => { + // If totp is enabled, show a prompt to regenerate totp + if (!totpEnabled.value) { + return + } + + const { isCanceled } = await reveal({ + title: 'Are you sure?', + text: 'If you continue your previous TOTP authenticator and recovery codes will no longer be valid.' + }) + + if(!isCanceled){ + ProcessAddOrUpdate() + } +} + +const disable = async () => { + // Show a confrimation prompt + const { isCanceled } = await reveal({ + title: 'Disable TOTP', + text: 'Are you sure you want to disable TOTP? You may re-enable TOTP later.' + }) + + if (isCanceled) { + return + } + + await elevatedApiCall(async ({ password }) => { + + // Disable the totp method + const res = await disableMethod(MfaMethod.TOTP, password) + res.getResultOrThrow() + + refreshMethods() + }) +} + +const VerifyTotp = async (code : string) => { + // Create a new TOTP instance from the current message + const totp = new TOTP(totpMessage.value) + + // validate the code + const valid = totp.validate({ token: code, window: 4 }) + + if (valid) { + showSubmitButton.value = true + toaster.success({ + title: 'Success', + text: 'Your TOTP code is valid and your account is now verified.' + }) + } else { + setMessage('Your TOTP code is not valid.') + } +} + +const CloseQrWindow = () => { + showSubmitButton.value = false + totpMessage.value = undefined + + //Fresh methods + refreshMethods() +} + +</script> + +<style> + +#totp-settings .otp-input input { + @apply w-12 text-center text-lg mx-1 focus:border-primary-500; +} + +</style> diff --git a/front-end/src/views/Blog/blog-api/index.ts b/front-end/src/views/Blog/blog-api/index.ts new file mode 100644 index 0000000..678883b --- /dev/null +++ b/front-end/src/views/Blog/blog-api/index.ts @@ -0,0 +1,22 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { ComputedChannels, ComputedContent, ComputedPosts } from '@vnuge/cmnext-admin' + +export interface BlogState { + readonly channels: ComputedChannels, + readonly posts: ComputedPosts, + readonly content: ComputedContent +}
\ No newline at end of file diff --git a/front-end/src/views/Blog/ckeditor/Editor.vue b/front-end/src/views/Blog/ckeditor/Editor.vue new file mode 100644 index 0000000..87c0595 --- /dev/null +++ b/front-end/src/views/Blog/ckeditor/Editor.vue @@ -0,0 +1,155 @@ +<template> + <div class="pt-6"> + <div class="flex justify-end w-full gap-2 my-2"> + <div class="w-fit"> + <Popover class="relative"> + <PopoverButton class="btn"> + Add + <fa-icon class="ml-2" icon="photo-film" /> + </PopoverButton> + <PopoverPanel class="absolute right-0 z-10 top-10"> + <div class="md-pannel"> + <div class=""> + Search for content by its id or file name. + </div> + <ContentSearch :blog="$props.blog"/> + </div> + </PopoverPanel> + </Popover> + </div> + <div class="w-fit"> + <Popover class="relative"> + <PopoverButton class="btn" @click="recoverMd"> + Markdown + <fa-icon class="ml-2" :icon="['fab','markdown']" /> + </PopoverButton> + <PopoverPanel class="absolute right-0 z-10 top-10"> + <div class="md-pannel"> + <div class=""> + Paste your markdown here to convert it to html. + </div> + <div class="my-4"> + <textarea class="w-full h-40 p-2 bg-transparent border" v-model="mdBuffer"></textarea> + </div> + <div class="flex justify-end"> + <button class="btn primary" @click="convertMarkdown">Convert</button> + </div> + </div> + </PopoverPanel> + </Popover> + </div> + <div class="w-fit"> + <button class="btn" @click="recoverFromCrash"> + Recover + <fa-icon class="ml-2" icon="rotate-left" /> + </button> + </div> + </div> + <div id="ck-editor-frame" ref="editorFrame"> + <div class="w-full text-center"> + <h5>Loading editor...</h5> + <fa-icon class="text-2xl" icon="spinner" spin /> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { debounce } from 'lodash'; +import { ref } from 'vue'; +import { useSessionStorage } from '@vueuse/core'; +import { tryOnMounted } from '@vueuse/shared'; +import { apiCall } from '@vnuge/vnlib.browser'; +import { Popover, PopoverButton, PopoverPanel } from '@headlessui/vue' +import { BlogState } from '../blog-api' +import { Converter } from 'showdown' + +//Import the editor config +import { config } from './build.ts' +import ContentSearch from '../components/ContentSearch.vue'; + +const emit = defineEmits(['change', 'load']) + +defineProps<{ + blog: BlogState +}>() + +let editor = {} +//Init new shodown converter +const showdownConverter = new Converter() +const mdBuffer = ref('') +const editorFrame = ref(null) +const crashBuffer = useSessionStorage('post-crash', '') + +const recoverFromCrash = () => { + //Set editor content from crash buffer + editor.setData(crashBuffer.value); +} + +const onChange = (content:string) =>{ + //Save the content to the crash buffer + crashBuffer.value = content; + emit('change', content) +} + +const convertMarkdown = () => { + + const html = showdownConverter.makeHtml(mdBuffer.value); + + //Set initial data + editor.setData(html) + + //manually trigger change event + onChange(html) + + //Clear the buffer + mdBuffer.value = '' +} + +const recoverMd = () => { + const current = editor.getData(); + const md = showdownConverter.makeMd(current); + mdBuffer.value = md; +} + +tryOnMounted(() => + //Load the editor once the component is mounted + apiCall(async ({ toaster }) => { + + //Entry script creates promise that resolves when the editor script is loaded + if(window.editorLoadResult){ + //Wait for the editor script to load + await (window.editorLoadResult as Promise<boolean>) + } + + if (!window['CKEDITOR']) { + toaster.general.error({ + title: 'Script Error', + text: 'The CKEditor script failed to load, check script permissions.' + }) + return; + } + + //CKEditor 5 superbuild in global scope + const { ClassicEditor } = window['CKEDITOR'] + + //Init editor when loading is complete + editor = await ClassicEditor.create(editorFrame.value, config); + + //Update the local copy when the editor data changes + editor.model.document.on('change:data', debounce(() => onChange(editor.getData())), 500) + + //Call initial load hook + emit('load', editor); + }) +) + +</script> + +<style lang="scss"> + +.md-pannel{ + @apply p-6 min-w-[32rem] bg-white shadow-md dark:bg-dark-700 border dark:border-dark-300; +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/ckeditor/build.ts b/front-end/src/views/Blog/ckeditor/build.ts new file mode 100644 index 0000000..83b46a7 --- /dev/null +++ b/front-end/src/views/Blog/ckeditor/build.ts @@ -0,0 +1,125 @@ +export const config = { + // https://ckeditor.com/docs/ckeditor5/latest/features/toolbar/toolbar.html#extended-toolbar-configuration-format + toolbar: { + items: [ + 'findAndReplace', 'selectAll', '|', + 'heading', '|', + 'bold', 'italic', 'strikethrough', 'underline', 'code', 'subscript', 'superscript', 'removeFormat', '|', + 'bulletedList', 'numberedList', 'todoList', '|', + 'outdent', 'indent', 'alignment', '|', + 'undo', 'redo', + '-', + 'fontSize', 'fontFamily', 'fontColor', 'fontBackgroundColor', 'highlight', '|', + 'link', 'insertImage', 'blockQuote', 'insertTable', 'mediaEmbed', 'codeBlock', 'htmlEmbed', '|', + 'specialCharacters', 'horizontalLine', 'pageBreak', '|', + 'exportPDF', 'exportWord', 'sourceEditing' + ], + shouldNotGroupWhenFull: true + }, + // Changing the language of the interface requires loading the language file using the <script> tag. + // language: 'es', + list: { + properties: { + styles: true, + startIndex: true, + reversed: true + } + }, + // https://ckeditor.com/docs/ckeditor5/latest/features/headings.html#configuration + heading: { + options: [ + { model: 'paragraph', title: 'Paragraph', class: 'ck-heading_paragraph' }, + { model: 'heading1', view: 'h1', title: 'Heading 1', class: 'ck-heading_heading1' }, + { model: 'heading2', view: 'h2', title: 'Heading 2', class: 'ck-heading_heading2' }, + { model: 'heading3', view: 'h3', title: 'Heading 3', class: 'ck-heading_heading3' }, + { model: 'heading4', view: 'h4', title: 'Heading 4', class: 'ck-heading_heading4' }, + { model: 'heading5', view: 'h5', title: 'Heading 5', class: 'ck-heading_heading5' }, + { model: 'heading6', view: 'h6', title: 'Heading 6', class: 'ck-heading_heading6' } + ] + }, + // https://ckeditor.com/docs/ckeditor5/latest/features/editor-placeholder.html#using-the-editor-configuration + placeholder: 'Welcome to CKEditor 5!', + // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-family-feature + fontFamily: { + options: [ + 'default', + 'Arial, Helvetica, sans-serif', + 'Courier New, Courier, monospace', + 'Georgia, serif', + 'Lucida Sans Unicode, Lucida Grande, sans-serif', + 'Tahoma, Geneva, sans-serif', + 'Times New Roman, Times, serif', + 'Trebuchet MS, Helvetica, sans-serif', + 'Verdana, Geneva, sans-serif' + ], + supportAllValues: true + }, + // https://ckeditor.com/docs/ckeditor5/latest/features/font.html#configuring-the-font-size-feature + fontSize: { + options: [10, 12, 14, 'default', 18, 20, 22], + supportAllValues: true + }, + // Be careful with the setting below. It instructs CKEditor to accept ALL HTML markup. + // https://ckeditor.com/docs/ckeditor5/latest/features/general-html-support.html#enabling-all-html-features + htmlSupport: { + allow: [ + { + name: /.*/, + attributes: true, + classes: true, + styles: true + } + ] + }, + // Be careful with enabling previews + // https://ckeditor.com/docs/ckeditor5/latest/features/html-embed.html#content-previews + htmlEmbed: { + showPreviews: true + }, + // https://ckeditor.com/docs/ckeditor5/latest/features/link.html#custom-link-attributes-decorators + link: { + decorators: { + addTargetToExternalLinks: true, + defaultProtocol: 'https://', + toggleDownloadable: { + mode: 'manual', + label: 'Downloadable', + attributes: { + download: 'file' + } + } + } + }, + // https://ckeditor.com/docs/ckeditor5/latest/features/mentions.html#configuration + mention: { + }, + // The "super-build" contains more premium features that require additional configuration, disable them below. + // Do not turn them on unless you read the documentation and know how to configure them and setup the editor. + removePlugins: [ + // These two are commercial, but you can try them out without registering to a trial. + // 'ExportPdf', + // 'ExportWord', + 'CKBox', + 'CKFinder', + 'EasyImage', + // This sample uses the Base64UploadAdapter to handle image uploads as it requires no configuration. + // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/base64-upload-adapter.html + // Storing images as Base64 is usually a very bad idea. + // Replace it on production website with other solutions: + // https://ckeditor.com/docs/ckeditor5/latest/features/images/image-upload/image-upload.html + // 'Base64UploadAdapter', + 'RealTimeCollaborativeComments', + 'RealTimeCollaborativeTrackChanges', + 'RealTimeCollaborativeRevisionHistory', + 'PresenceList', + 'Comments', + 'TrackChanges', + 'TrackChangesData', + 'RevisionHistory', + 'Pagination', + 'WProofreader', + // Careful, with the Mathtype plugin CKEditor will not load when loading this sample + // from a local file system (file://) - load this site via HTTP server if you enable MathType + 'MathType' + ], +}
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Channels.vue b/front-end/src/views/Blog/components/Channels.vue new file mode 100644 index 0000000..ad88e50 --- /dev/null +++ b/front-end/src/views/Blog/components/Channels.vue @@ -0,0 +1,99 @@ +<template> + <div id="channel-editor"> + <EditorTable title="Manage channels" :show-edit="showEdit" :pagination="pagination" @open-new="openNew"> + <template v-slot:table> + <ChannelTable + :channels="items" + @open-edit="openEdit" + /> + </template> + <template #editor> + <ChannelEdit + :blog="$props.blog" + @close="closeEdit" + @on-submit="onSubmit" + @on-delete="onDelete" + /> + </template> + </EditorTable> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue'; +import { BlogState } from '../blog-api'; +import { isEmpty, filter as _filter } from 'lodash'; +import { apiCall } from '@vnuge/vnlib.browser'; +import { BlogChannel, ChannelFeed, useFilteredPages } from '@vnuge/cmnext-admin'; +import ChannelEdit from './Channels/ChannelEdit.vue'; +import ChannelTable from './Channels/ChannelTable.vue'; +import EditorTable from './EditorTable.vue'; + +const emit = defineEmits(['close', 'reload']) + +const props = defineProps<{ + blog: BlogState, +}>() + +const { updateChannel, addChannel, deleteChannel, getQuery } = props.blog.channels; +const { channelEdit } = getQuery() + +//Setup channel filter +const { items, pagination } = useFilteredPages(props.blog.channels, 15) + +const showEdit = computed(() => !isEmpty(channelEdit.value)) + +const openEdit = (channel: BlogChannel) => channelEdit.value = channel.id; + +const closeEdit = (update?:boolean) => { + channelEdit.value = '' + //reload channels + if(update){ + emit('reload') + } + //Reset page to top + window.scrollTo(0, 0) +} + +const openNew = () => { + channelEdit.value = 'new' + //Reset page to top + window.scrollTo(0, 0) +} + +const onSubmit = async ({channel, feed} : { channel:BlogChannel, feed? : ChannelFeed}) => { + + //Check for new channel, or updating old channel + if(channelEdit.value === 'new'){ + //Exec create call + await apiCall(async () => { + await addChannel(channel, feed); + //Close the edit panel + closeEdit(true); + }) + } + else if(!isEmpty(channelEdit.value)){ + //Exec update call + await apiCall(async () => { + await updateChannel(channel, feed); + //Close the edit panel + closeEdit(true); + }) + } + //Notify error state +} + +const onDelete = async (channel : BlogChannel) => { + //Exec delete call + await apiCall(async () => { + await deleteChannel(channel); + //Close the edit panel + closeEdit(true); + }) +} + +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Channels/ChannelEdit.vue b/front-end/src/views/Blog/components/Channels/ChannelEdit.vue new file mode 100644 index 0000000..56376fe --- /dev/null +++ b/front-end/src/views/Blog/components/Channels/ChannelEdit.vue @@ -0,0 +1,157 @@ +<template> + <div class="flex flex-col w-full"> + <div class="my-4 ml-auto"> + <div class="button-group"> + <button class="btn primary" form="channel-edit-form">Save</button> + <button class="btn" @click="close">Cancel</button> + </div> + </div> + <div class="mx-auto"> + <h4 v-if="editMode" class="text-center">Edit Channel</h4> + <h4 v-else class="text-center">Create Channel</h4> + <p> + Your root directory and index file name must be unique within your S3 bucket. + </p> + </div> + <dynamic-form + id="channel-edit-form" + class="mx-auto" + :form="channelSchema" + :validator="channelVal.v$" + @submit="onSubmit" + /> + <div class="relative"> + <div class="absolute top-0 right-10"> + <button class="btn xs no-border red" @click="disableFeed" v-if="feedEnabled"> + Disable + </button> + </div> + </div> + <div class="max-w-xl mx-auto mt-6"> + <h4 v-if="editMode" class="text-center">Edit Feed</h4> + <h4 v-else class="text-center">Create Feed</h4> + <p> + Optionally define the rss feed for this channel. If you do not configure the feed, posts + to this channel will not be published to an rss feed, you may configure this feed at any time. + </p> + </div> + + <!-- Feed edit form --> + <dynamic-form + id="feed-edit-form" + class="mx-auto mt-4" + :form="feedSchema" + :validator="feedVal.v$" + @submit="onSubmit" + /> + + <!-- Feed properties --> + <FeedFields :properties="feedProps" /> + + <div class="mt-6"> + <div class="mx-auto w-fit"> + <button class="btn red" @click="onDelete" v-if="editMode"> + Delete Permenantly + </button> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue'; +import { BlogState } from '../../blog-api'; +import { forEach, isEmpty, cloneDeep, isNil } from 'lodash'; +import { reactiveComputed } from '@vueuse/core'; +import { useConfirm } from '@vnuge/vnlib.browser'; +import FeedFields from '../FeedFields.vue'; +import { BlogChannel, ChannelFeed, useXmlProperties } from '@vnuge/cmnext-admin'; +import { getChannelForm } from '../../form-helpers'; + +const emit = defineEmits(['close', 'onSubmit', 'onDelete']) + +const props = defineProps<{ + blog: BlogState +}>() + +//Disallow empty channels +const channel = computed(() => props.blog.channels.editChannel.value || {} as BlogChannel) +const editMode = computed(() => !isNil(channel.value.id)) + +const { getChannelValidator, channelSchema, feedSchema, getFeedValidator } = getChannelForm(editMode); +const { reveal } = useConfirm(); + +//Get the feed properties +const feedProps = useXmlProperties(computed(() => channel.value.feed)); + +//Must have reactive buffers for the channel and feed +const channelBuffer = reactiveComputed(() => cloneDeep(channel.value) as BlogChannel) +const feedBuffer = reactiveComputed(() => cloneDeep(channel.value.feed || {}) as ChannelFeed) + +//Get validators for channel and feed +const channelVal = getChannelValidator(channelBuffer); +const feedVal = getFeedValidator(feedBuffer); + +const feedEnabled = computed(() => !isEmpty(feedBuffer.url)) + +const disableFeed = () => { + //Clear the feed + forEach(feedBuffer, (_value, key) => feedBuffer[key] = null) + //Reset the feed validator + feedVal.reset(); +} + +const onSubmit = async () => { + //validate + if(!await channelVal.validate()){ + return; + } + + //Feed may not be defined, if it is validate it + if(feedEnabled.value){ + + if(!await feedVal.validate()){ + return; + } + + //set/overwite feed properties + const feed = { + ...feedBuffer, + properties:feedProps.getCurrentProperties() + } + + //Invoke submitted with feed + emit('onSubmit', { channel: channelBuffer, feed }) + } + else{ + //Invoke submitted without feed + emit('onSubmit', { channel: channelBuffer, feed: null}) + } + +} + +const onDelete = async () => { + //Show confirm + const { isCanceled } = await reveal({ + title: 'Delete Channel?', + text: 'Are you sure you want to delete this channel? This action cannot be undone.', + }) + if(isCanceled){ + return; + } + + if(!confirm('Are you sure you want to delete this channel forever?')){ + return; + } + + emit('onDelete', channelBuffer) +} + +const close = () => emit('close') + +</script> + +<style lang="scss"> + + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Channels/ChannelTable.vue b/front-end/src/views/Blog/components/Channels/ChannelTable.vue new file mode 100644 index 0000000..cdf15e0 --- /dev/null +++ b/front-end/src/views/Blog/components/Channels/ChannelTable.vue @@ -0,0 +1,48 @@ +<template> + <thead> + <tr> + <th>Name</th> + <th>Path</th> + <th>Index</th> + <th>Feed?</th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="channel in channels" :key="channel.id" class="table-row"> + <td> + {{ channel.name }} + </td> + <td> + {{ channel.path }} + </td> + <td> + {{ channel.index }} + </td> + <td> + {{ feedEnabled(channel) }} + </td> + <td class="w-12"> + <button class="btn xs no-border" @click="openEdit(channel)"> + <fa-icon icon="pencil" /> + </button> + </td> + </tr> + </tbody> +</template> + +<script setup lang="ts"> +import { BlogChannel } from '@vnuge/cmnext-admin'; +import { toRefs } from 'vue'; +const emit = defineEmits(['open-edit']) + +const props = defineProps<{ + channels:BlogChannel[] +}>() + +const { channels } = toRefs(props) + +const feedEnabled = (channel: BlogChannel) => channel.feed ? 'Enabled' : 'Disabled' +const openEdit = (channel: BlogChannel) => emit('open-edit', channel) + +</script> diff --git a/front-end/src/views/Blog/components/Content.vue b/front-end/src/views/Blog/components/Content.vue new file mode 100644 index 0000000..00f8602 --- /dev/null +++ b/front-end/src/views/Blog/components/Content.vue @@ -0,0 +1,133 @@ +<template> + <div id="content-editor" class=""> + <EditorTable title="Manage content" :show-edit="showEdit" :pagination="pagination" @open-new="openNew"> + <template v-slot:table> + <ContentTable + :content="items" + @open-edit="openEdit" + @copy-link="copyLink" + /> + </template> + <template #editor> + <ContentEditor + :blog="$props.blog" + @submit="onSubmit" + @close="closeEdit" + @delete="onDelete" + /> + </template> + </EditorTable> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue'; +import { BlogState } from '../blog-api'; +import { isEmpty } from 'lodash'; +import { apiCall } from '@vnuge/vnlib.browser'; +import EditorTable from './EditorTable.vue'; +import ContentEditor from './Content/ContentEditor.vue'; +import ContentTable from './Content/ContentTable.vue'; +import { useClipboard } from '@vueuse/core'; +import { ContentMeta, useFilteredPages } from '@vnuge/cmnext-admin'; + +const emit = defineEmits(['reload']) + +const props = defineProps<{ + blog: BlogState +}>() + + +//Get the computed content +const { selectedId, + updateContent, + uploadContent, + deleteContent, + updateContentName, + getPublicUrl + } = props.blog.content; + + //Setup content filter + const { items, pagination } = useFilteredPages(props.blog.content, 15) + +const showEdit = computed(() => !isEmpty(selectedId.value)); + +const openEdit = async (item: ContentMeta) => selectedId.value = item.id + +const closeEdit = (update?: boolean) => { + selectedId.value = '' + //reload channels + if (update) { + emit('reload') + } + //Reset page to top + window.scrollTo(0, 0) +} + +const openNew = () => { + selectedId.value = 'new' + //Reset page to top + window.scrollTo(0, 0) +} + +interface OnSubmitValue{ + item: ContentMeta, + file: File | undefined +} + +//Allow copying of the public url to clipboard +const { copy } = useClipboard() +const copyLink = async (item : ContentMeta) =>{ + apiCall(async ({toaster}) =>{ + const url = await getPublicUrl(item); + await copy(url); + toaster.general.info({ title: 'Copied link to clipboard' }) + }); +} + +const onSubmit = async (value : OnSubmitValue) => { + + //Check for new channel, or updating old channel + if (selectedId.value === 'new') { + //Exec create call + await apiCall(async () => { + + if(!value.file?.name){ + throw Error('No file selected') + } + + //endpoint returns the content + await uploadContent(value.file, value.item.name!); + + //Close the edit panel + closeEdit(true); + }) + } + else if (!isEmpty(selectedId.value)) { + //Exec update call + await apiCall(async () => { + //If no file was attached, just update the file name + if(value.file?.name){ + await updateContent(value.item, value.file); + } + else{ + await updateContentName(value.item, value.item.name!); + } + //Close the edit panel + closeEdit(true); + }) + } + //Notify error state +} + +const onDelete = async (item: ContentMeta) => { + //Exec delete call + await apiCall(async () => { + await deleteContent(item); + //Close the edit panel + closeEdit(true); + }) +} + + +</script>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Content/ContentEditor.vue b/front-end/src/views/Blog/components/Content/ContentEditor.vue new file mode 100644 index 0000000..4de7f8a --- /dev/null +++ b/front-end/src/views/Blog/components/Content/ContentEditor.vue @@ -0,0 +1,225 @@ +<template> + <div id="content-editor" class="flex flex-col w-full"> + <div class="my-4 ml-auto"> + <div class="button-group"> + <!-- Submit the post form --> + <button :disabled="waiting" class="btn primary" form="content-upload-form"> + <fa-icon icon="spinner" v-if="waiting" class="animate-spin" /> + <span v-else>Save</span> + </button> + <button class="btn" @click="onClose">Cancel</button> + </div> + </div> + <div class="mx-auto sm:min-w-[20rem]"> + <h4 class="text-center">Edit Content</h4> + <p> + Add or edit content file + </p> + <div class="mt-3 text-sm"> + Publishing to: + </div> + <div class="p-2 px-3 bg-gray-200 border border-gray-300 rounded-md dark:bg-transparent"> + {{ selectedChannelName }} + </div> + </div> + <div id="content-edit-body" class="min-h-[24rem] my-10"> + <form id="content-upload-form" class="flex" @submit.prevent="onSubmit"> + <fieldset class="mx-auto flex flex-col gap-10 w-[32rem]"> + <div class="flex flex-col"> + <div class="p-3 py-0.5"> + <label class="">File name</label> + <input + type="text" + class="w-full input primary" + placeholder="Title" + v-model="v$.name.$model" + :class="{'invalid':v$.name.$invalid && v$.name.$dirty}" + /> + </div> + <div v-if="editFile?.id" class="mt-3"> + <div class="p-3 py-0.5"> + <label>Content Id</label> + <input type="text" class="w-full input primary" :value="editFile.id" readonly /> + </div> + </div> + + <div v-if="uploadedFile.name" class="border border-gray-300 p-4 w-[24rem] mx-auto rounded-sm relative mt-5"> + <div class="absolute top-0 text-right -right-12"> + <button class="rounded-sm btn sm red" @click.prevent="removeNewFile"> + <fa-icon :icon="['fas', 'trash']" /> + </button> + </div> + <div class=""> + Name: + <span class="border-b border-coolGray-400"> + {{ getFileName(uploadedFile) }} + </span> + </div> + <div class="mt-3"> + Size: {{ getFileSize(uploadedFile) }} + </div> + <div class="mt-3"> + Content-Type: {{ getContentType(uploadedFile) }} + </div> + </div> + <div v-else-if="editFile?.id" > + <div class="border border-gray-300 p-4 min-w-[24rem] mx-auto rounded-sm relative mt-5"> + <div class=""> + Name: {{ getFileName(editFile) }} + </div> + <div class="mt-3"> + Size: {{ getSizeinKb(editFile?.length) }} + </div> + <div class="mt-3"> + File Path: {{ editFile.path }} + </div> + <div class="mt-3"> + Content-Type: {{ editFile.content_type }} + </div> + </div> + </div> + <div v-if="!uploadedFile.name" class="m-auto mt-5 w-fit"> + <button class="btn" @click.prevent="open()"> + {{ editFile?.id ? 'Overwrite file' : 'Select File' }} + </button> + </div> + </div> + </fieldset> + </form> + </div> + <div class="mt-4"> + <div class="mx-auto w-fit"> + <button class="btn red" @click="onDelete">Delete Forever</button> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { computed, ref } from 'vue'; +import { reactiveComputed, useFileDialog } from '@vueuse/core'; +import { ContentMeta } from '@vnuge/cmnext-admin'; +import { useConfirm, useVuelidateWrapper, useFormToaster, useWait } from '@vnuge/vnlib.browser'; +import { defaultTo, first, isEmpty, round, truncate } from 'lodash'; +import { required, helpers, maxLength } from '@vuelidate/validators' +import useVuelidate from '@vuelidate/core'; +import { BlogState } from '../../blog-api'; + +const emit = defineEmits(['close', 'submit', 'delete']); +const props = defineProps<{ + blog: BlogState +}>(); + +const { reveal } = useConfirm(); +const { waiting } = useWait(); + +const { content, channels } = props.blog; + +const selectedId = computed(() => content.selectedId.value); +const selectedContent = computed<ContentMeta>(() => defaultTo(content.selectedItem.value, {} as ContentMeta)); +const metaBuffer = reactiveComputed<ContentMeta>(() => ({ ...selectedContent.value})); +const selectedChannelName = computed(() => defaultTo(channels.selectedItem.value?.name, 'No channel selected')); + +const v$ = useVuelidate({ + name: { + required, + maxLen:maxLength(50), + reg: helpers.withMessage('The file name contains invalid characters', helpers.regex(/^[a-zA-Z0-9 \-\.]*$/)) + }, +}, metaBuffer) + +const { validate } = useVuelidateWrapper(v$); + +const file = ref<File | undefined>(); +const { files, open, reset, onChange } = useFileDialog({ accept: '*' }) +//update the file buffer when a user selects a file to upload +onChange(() => { + file.value = first(files.value) + v$.value.name.$model = file.value?.name; +}) + +const editFile = computed<ContentMeta | undefined>(() => selectedContent.value); +const uploadedFile = computed<File>(() => defaultTo(file.value, {} as File)); + +const getFileName = (file : File | ContentMeta) => truncate(file.name, { length: 20 }); +const getFileSize = (file : File) => { + const size = round(file.size > 1024 ? file.size / 1024 : file.size, 2); + return `${size} ${file.size > 1024 ? 'KB' : 'B'}`; +} +const getContentType = (file : File) => file.type; +const getSizeinKb = (value : number | undefined) => { + value = defaultTo(value, 0); + const size = round(value > 1024 ? value / 1024 : value, 2); + return `${size} ${value > 1024 ? 'KB' : 'B'}`; +} + +const onSubmit = async () => { + + const { error } = useFormToaster() + const hasFile = !isEmpty(file.value?.name); + + //Validate the form + if(!await validate()){ + return; + } + + //Check if in edit mode + if(selectedId.value === 'new'){ + //New file upload + if(!hasFile){ + error({ title: 'No file selected' }) + return; + } + } + //Edit mode + else{ + //If a new file has been attached, then we should prompt for an overwrite + if(hasFile){ + //Confirm overwrite + const { isCanceled } = await reveal({ + title: 'Overwrite file?', + text: 'Are you sure you want to overwrite the file? This action cannot be undone.', + }) + if (isCanceled) { + return; + } + } + } + emit('submit', { item: metaBuffer, file: file.value }); +} + +const onClose = () => emit('close'); + +const onDelete = async () => { + //Show confirm + const { isCanceled } = await reveal({ + title: 'Delete File?', + text: 'Are you sure you want to delete this file? This action cannot be undone.', + }) + if (isCanceled) { + return; + } + + if (!confirm('Are you sure you want to delete this file forever?')) { + return; + } + + //Emit the delete event with the original post + emit('delete', metaBuffer) +} + +const removeNewFile = () =>{ + file.value = undefined; + v$.value.name.$model = editFile.value?.name ?? ''; + reset(); +} + +</script> + +<style lang="scss"> +#content-upload-form{ + input.primary.invalid{ + @apply border-red-500; + } +} +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Content/ContentTable.vue b/front-end/src/views/Blog/components/Content/ContentTable.vue new file mode 100644 index 0000000..c47a063 --- /dev/null +++ b/front-end/src/views/Blog/components/Content/ContentTable.vue @@ -0,0 +1,75 @@ +<template> + <thead> + <tr> + <th>File Name</th> + <th>Id</th> + <th>Date</th> + <th>Content Type</th> + <th>Length</th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="item in content" :key="item.id" class="table-row"> + <td> + {{ getItemName(item) }} + </td> + <td> + {{ getItemId(item) }} + </td> + <td> + {{ getDateString(item.date) }} + </td> + <td> + {{ item.content_type }} + </td> + <td> + {{ getItemLength(item) }} + </td> + <td class="w-24"> + <fieldset :disabled="waiting"> + <button class="btn xs no-border" @click="copyLink(item)"> + <fa-icon icon="link" /> + </button> + <button class="btn xs no-border" @click="copy(item.id)"> + <fa-icon icon="copy" /> + </button> + <button class="btn xs no-border" @click="openEdit(item)"> + <fa-icon icon="pencil" /> + </button> + </fieldset> + </td> + </tr> + </tbody> +</template> + +<script setup lang="ts"> +import { toRefs } from 'vue'; +import { filter as _filter, truncate } from 'lodash'; +import { useClipboard } from '@vueuse/core'; +import { useWait } from '@vnuge/vnlib.browser'; +import { ContentMeta } from '@vnuge/cmnext-admin'; + +const emit = defineEmits(['open-edit', 'copy-link']) + +const props = defineProps<{ + content: ContentMeta[] +}>() + +const { content } = toRefs(props) + +const { waiting } = useWait() +const { copy } = useClipboard() + +const getDateString = (time?: number) => new Date((time || 0) * 1000).toLocaleString(); +const getItemLength = (item: ContentMeta) : string =>{ + const length = item.length || 0; + return length > 1024 ? `${(length / 1024).toFixed(2)} KB` : `${length} B` +} +const getItemId = (item: ContentMeta) => truncate(item.id || '', { length: 20 }) +const getItemName = (item : ContentMeta) => truncate(item.name || '', { length: 30 }) + +const openEdit = async (item: ContentMeta) => emit('open-edit', item) +const copyLink = (item : ContentMeta) => emit('copy-link', item) + +</script>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/ContentSearch.vue b/front-end/src/views/Blog/components/ContentSearch.vue new file mode 100644 index 0000000..37fd438 --- /dev/null +++ b/front-end/src/views/Blog/components/ContentSearch.vue @@ -0,0 +1,115 @@ +<template> + <div id="content-search" class="my-4"> + <div class=""> + <div class=""> + <input class="w-full input primary" placeholder="Search..." v-model="search" /> + </div> + </div> + <div class="search-results"> + <div v-if="searchResults.length == 0" class="result"> + No results found. + </div> + <div v-else v-for="result in searchResults" :key="result.id" @click.prevent="onSelected(result)" class="result"> + <div class="flex-auto result name"> + {{ result.shortName }} + </div> + <div class="result id"> + {{ result.shortId }} + </div> + <div class="rseult controls"> + <div v-if="waiting"> + <fa-icon icon="spinner" spin /> + </div> + <div v-else-if="result.copied.value" class="text-sm text-amber-500"> + copied + </div> + <div v-else class=""> + <button class="btn secondary sm borderless" @click="result.copyLink()"> + <fa-icon icon="link" /> + </button> + </div> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { useClipboard } from '@vueuse/core'; +import { apiCall, useWait } from '@vnuge/vnlib.browser'; +import { computed, Ref, ref } from 'vue'; +import { map, slice, truncate } from 'lodash'; +import { ContentMeta } from '@vnuge/cmnext-admin'; +import { BlogState } from '../blog-api'; + +const emit = defineEmits(['selected']) + +const props = defineProps<{ + blog: BlogState, +}>() + +const { createReactiveSearch, getPublicUrl } = props.blog.content +const { waiting } = useWait() + +const search = ref('') +const searcher = createReactiveSearch(search); + +interface ContentResult extends ContentMeta { + readonly shortId: string, + readonly shortName: string, + readonly copied: Ref<boolean>, + copyLink(): void +} + +const searchResults = computed<ContentResult[]>(() => { + const current = slice(searcher.value, 0, 5); + + //Copies the link to the clipboard from the server to insert into the editor + const copyLink = (result : ContentMeta, copy : (text: string) => Promise<void> ) => { + apiCall(async () =>{ + const link = await getPublicUrl(result); + await copy(link); + }) + } + + //Formats the result for display + return map(current, content => { + //scoped clipboard for copy link + const { copied, copy } = useClipboard(); + return { + ...content, + //truncate the id and name for display + shortId: truncate(content.id, { length: 15 }), + shortName: truncate(content.name, { length: 24 }), + copyLink: () => copyLink(content, copy), + copied + } + }) +}) + +const onSelected = (result: ContentResult) => { + emit('selected', result) +} + +</script> + +<style lang="scss"> + + .search-results{ + @apply mt-3; + } + + .result{ + @apply flex flex-row items-center justify-between; + @apply p-1 cursor-pointer hover:bg-gray-100 dark:hover:bg-dark-600; + + .id{ + @apply text-sm; + } + + .controls{ + @apply min-w-[4rem] text-center; + } + } + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/EditorTable.vue b/front-end/src/views/Blog/components/EditorTable.vue new file mode 100644 index 0000000..4ec5a33 --- /dev/null +++ b/front-end/src/views/Blog/components/EditorTable.vue @@ -0,0 +1,96 @@ +<template> + <slot class="flex flex-row"> + <div class="flex-1 px-4 mt-3"> + <div v-if="!showEdit" class=""> + <div class="flex justify-between p-4 pt-0"> + <div class="w-[20rem]"> + <h4>{{ $props.title }}</h4> + </div> + <div class="h-full"> + <div :class="{'opacity-100':waiting}" class="opacity-0"> + <fa-icon icon="spinner" class="animate-spin" /> + </div> + </div> + <div class="mt-auto"> + <div class="flex justify-center"> + <nav aria-label="Pagination"> + <ul class="inline-flex items-center space-x-1 text-sm rounded-md"> + <li> + <button :disabled="isFirstPage" class="page-button" @click="prev"> + <fa-icon icon="chevron-left" /> + </button> + </li> + <li> + <span class="inline-flex items-center px-4 py-2 space-x-1"> + Page + <b class="mx-1"> + {{ currentPage }} + </b> + of + <b class="ml-1"> + {{ pageCount }} + </b> + </span> + </li> + <li> + <button :disabled="isLastPage" class="page-button" @click="next"> + <fa-icon icon="chevron-right" /> + </button> + </li> + </ul> + </nav> + </div> + </div> + + <div class="h-fit"> + <button class="rounded btn primary sm" @click="openNew"> + <fa-icon :icon="['fas', 'plus']" class="mr-2" /> + New + </button> + </div> + </div> + <table class="edit-table"> + <slot name="table" /> + </table> + </div> + <div v-else class=""> + <slot name="editor" /> + </div> + </div> + </slot> +</template> + +<script setup lang="ts"> +import { toRefs } from 'vue'; +import { useWait } from '@vnuge/vnlib.browser'; +import { UseOffsetPaginationReturn } from '@vueuse/core'; + +const emit = defineEmits(['open-new']) +const props = defineProps<{ + title: string, + showEdit: boolean, + pagination: UseOffsetPaginationReturn +}>() + +const { showEdit } = toRefs(props) + +const { waiting } = useWait() + +//Get pagination +const { pageCount, next, prev, isLastPage, isFirstPage, currentPage } = props.pagination + +const openNew = () => { + emit('open-new') +} + +</script> + +<style lang="scss"> + +button.page-button{ + @apply inline-flex items-center px-2 py-1.5 space-x-2 font-medium; + @apply text-gray-500 bg-white border border-gray-300 rounded-full hover:bg-gray-50; + @apply dark:border-dark-300 dark:bg-transparent dark:text-gray-300 hover:dark:bg-dark-700; +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/FeedFields.vue b/front-end/src/views/Blog/components/FeedFields.vue new file mode 100644 index 0000000..90e1454 --- /dev/null +++ b/front-end/src/views/Blog/components/FeedFields.vue @@ -0,0 +1,140 @@ +<template> + <div id="feed-custom-fields"> + <div class="my-3 text-center"> + <h4>Feed custom fields</h4> + </div> + + <div v-if="cleanXml" class="w-full max-w-2xl mx-auto"> + <pre class="xml"> +{{ cleanXml }} + </pre> + </div> + + + <div class="my-2 ml-auto w-fit"> + <div v-if="!editMode" class="button-group"> + <button class="btn" @click="edit">Edit</button> + </div> + <div v-else class="button-group"> + <button class="btn primary" @click="save" >Update</button> + <button class="btn" @click="cancel">Cancel</button> + </div> + </div> + + + <div v-if="editMode" class="flex flex-col"> + <div v-if="$props.blog" class="mb-2"> + <EpAdder :blog="$props.blog" @submit="onAddEnclosure" /> + </div> + + <div class=""> + <JsonEditorVue :ask-to-format="true" class="json" v-model="jsonFeedData"/> + </div> + </div> + + </div> +</template> + +<script setup lang="ts"> +import { computed, ref } from 'vue'; +import { FeedProperty, UseXmlProperties } from '@vnuge/cmnext-admin'; +import { BlogState } from '../blog-api'; +import JsonEditorVue from 'json-editor-vue' +import EpAdder from './podcast-helpers/EpisodeAdder.vue'; + +const props = defineProps<{ + properties: UseXmlProperties, + blog?: BlogState +}>() + +const { getXml, saveJson, getModel, addProperties } = props.properties + +const jsonFeedData = ref() +const editMode = ref(false) +const xmlData = ref<string | undefined>(getXml()) + +const cleanXml = computed(() => { + + const formatXml = (xml : string) => { // tab = optional indent value, default is tab (\t) + var formatted = '', indent = ''; + xml.split(/>\s*</).forEach(function (node) { + if (node.match(/^\/\w/)){ + indent = indent.substring(1); // decrease indent by one 'tab' + } + formatted += indent + '<' + node + '>\r\n'; + if (node.match(/^<?\w[^>]*[^\/]$/)){ + indent += '\t'; // increase indent + } + }); + + return formatted.substring(1, formatted.length - 3); + } + return formatXml(xmlData.value || '') +}) + +const edit = () => { + jsonFeedData.value = getModel() + editMode.value = true +} + +const save = () : void => { + //Only close editor if the json is valid + if(saveJson(jsonFeedData.value)){ + editMode.value = false + //update xml + xmlData.value = getXml() + } +} + +const cancel = () : void => { + editMode.value = false + xmlData.value = getXml() +} + +const onAddEnclosure = (props: FeedProperty[]) =>{ + addProperties(props); + //update xml + xmlData.value = getXml() + //update json editor + jsonFeedData.value = getModel() +} + +</script> + +<style lang="scss"> + +#feed-custom-fields{ + + @apply w-full max-w-[80%] mx-auto py-4 my-5; + + .json > .jse-main{ + @apply w-full min-h-[40rem] rounded bg-transparent mx-auto; + } + + .feed-fields{ + @apply mx-auto gap-4 flex flex-row justify-center my-6; + + input.primary{ + @apply w-full; + } + + textarea.primary{ + @apply w-full h-full tracking-wider font-mono p-3 text-sm; + } + + textarea.invalid{ + @apply border-red-500; + } + } + + .xml{ + @apply tracking-wider font-mono p-3 text-sm border dark:border-dark-500 rounded whitespace-pre-wrap; + } + + .xml.invalid{ + @apply border-red-500; + } + +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts.vue b/front-end/src/views/Blog/components/Posts.vue new file mode 100644 index 0000000..5ebeeac --- /dev/null +++ b/front-end/src/views/Blog/components/Posts.vue @@ -0,0 +1,113 @@ +<template> + <div id="post-editor" class=""> + <EditorTable title="Manage posts" :show-edit="showEdit" :pagination="pagination" @open-new="openNew"> + <template v-slot:table> + <PostTable + :posts="items" + @open-edit="openEdit" + /> + </template> + <template #editor> + <PostEditor + :blog="$props.blog" + @submit="onSubmit" + @close="closeEdit" + @delete="onDelete" + /> + </template> + </EditorTable> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue'; +import { isEmpty } from 'lodash'; +import { PostMeta, useFilteredPages } from '@vnuge/cmnext-admin'; +import { apiCall, debugLog } from '@vnuge/vnlib.browser'; +import EditorTable from './EditorTable.vue'; +import PostEditor from './Posts/PostEdit.vue'; +import PostTable from './Posts/PostTable.vue'; +import { BlogState } from '../blog-api'; + +const emit = defineEmits(['reload']) + +const props = defineProps<{ + blog: BlogState +}>() + +const { selectedId, publishPost, updatePost, deletePost } = props.blog.posts; +const { updatePostContent } = props.blog.content; + +const showEdit = computed(() => !isEmpty(selectedId.value)); + +//Init paginated items for the table and use filtered items +const { pagination, items } = useFilteredPages(props.blog.posts, 15) + +//Open with the post id +const openEdit = async (post: PostMeta) => selectedId.value = post.id; + +const closeEdit = (update?: boolean) => { + selectedId.value = '' + //reload channels + if (update) { + emit('reload') + } + //Reset page to top + window.scrollTo(0, 0) +} + +const openNew = () => { + //Reset the edit post + selectedId.value = 'new' + //Reset page to top + window.scrollTo(0, 0) +} + +const onSubmit = async ({post, content } : { post:PostMeta, content:string }) => { + + debugLog('submitting', post, content); + + //Check for new channel, or updating old channel + if (selectedId.value === 'new') { + //Exec create call + await apiCall(async () => { + + //endpoint returns the content + const newMeta = await publishPost(post); + + //Publish the content + await updatePostContent(newMeta, content) + + //Close the edit panel + closeEdit(true); + }) + } + else if (!isEmpty(selectedId.value)) { + //Exec update call + await apiCall(async () => { + await updatePost(post); + + //Publish the content + await updatePostContent(post, content) + + //Close the edit panel + closeEdit(true); + }) + } + //Notify error state +} + +const onDelete = async (post: PostMeta) => { + //Exec delete call + await apiCall(async () => { + await deletePost(post); + //Close the edit panel + closeEdit(true); + }) +} + +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts/PostEdit.vue b/front-end/src/views/Blog/components/Posts/PostEdit.vue new file mode 100644 index 0000000..4f7b52b --- /dev/null +++ b/front-end/src/views/Blog/components/Posts/PostEdit.vue @@ -0,0 +1,155 @@ +<template> + <div id="new-post-editor" class="flex flex-col w-full"> + <div class="my-4 ml-auto"> + <div class="button-group"> + <!-- Submit the post form --> + <button class="btn primary" form="post-edit-form">Save</button> + <button class="btn" @click="onClose">Cancel</button> + </div> + </div> + <div class="mx-auto"> + <h4 class="text-center">Edit Post</h4> + <p> + Add or edit a post to publish to your blog. + </p> + </div> + <div class="relative"> + <div class="absolute top-2 right-10"> + <button class="btn no-border" @click="setMeAsAuthor">@Me</button> + </div> + </div> + <dynamic-form + id="post-edit-form" + class="mx-auto" + :form="schema" + :disabled="false" + :validator="v$" + @submit="onSubmit" + /> + + <div id="post-content-editor" class="px-6" :class="{'invalid':v$.content.$invalid}"> + <Editor @change="onContentChanged" :blog="$props.blog" @load="onEditorLoad" /> + </div> + + <FeedFields :properties="postProperties" :blog="$props.blog" /> + + <div class="mx-auto my-4"> + <div class="button-group"> + <!-- Submit the post form --> + <button class="btn primary" form="post-edit-form">Save</button> + <button class="btn" @click="onClose">Cancel</button> + <button v-if="!isNew" class="btn red" @click="onDelete">Delete Forever</button> + </div> + </div> + </div> +</template> +<script setup lang="ts"> +import { computed } from 'vue'; +import { BlogState } from '../../blog-api'; +import { reactiveComputed } from '@vueuse/core'; +import { isNil, isString, split } from 'lodash'; +import { PostMeta, useXmlProperties } from '@vnuge/cmnext-admin'; +import { apiCall, useConfirm, useUser } from '@vnuge/vnlib.browser'; +import { getPostForm } from '../../form-helpers'; +import Editor from '../../ckeditor/Editor.vue'; +import FeedFields from '../FeedFields.vue'; + +const emit = defineEmits(['close', 'submit', 'delete']); +const props = defineProps<{ + blog: BlogState +}>() + +const { reveal } = useConfirm(); +const { getProfile } = useUser(); +const { schema, getValidator } = getPostForm(); + +const { posts, content } = props.blog; + +const isNew = computed(() => isNil(posts.selectedItem.value)); + +/* Post meta may load delayed from the api so it must be computed +and reactive, it may also be empty when a new post is created */ +const postBuffer = reactiveComputed<PostMeta>(() => { + return { + ...posts.selectedItem.value, + content: '' + } as PostMeta +}); + +const { v$, validate } = getValidator(postBuffer); + +//Wrap the post properties in an xml feed editor +const postProperties = useXmlProperties(posts.selectedItem); + +const onSubmit = async () =>{ + if(!await validate()){ + return; + } + + //get all properties + const p = postProperties.getCurrentProperties(); + + const post = { + ...postBuffer, + properties: p, + content: undefined + } + + //Remove the content from the post object + delete post.content; + + //Convert the tags string to an array of strings + post.tags = isString(post.tags) ? split(post.tags, ',') : post.tags; + + emit('submit', { post, content: v$.value.content.$model}); +} + +const onClose = () => emit('close'); + +const onContentChanged = (content: string) => { + //Set the validator content string + v$.value.content.$model = content; +} + +const onDelete = async () => { + //Show confirm + const { isCanceled } = await reveal({ + title: 'Delete Post?', + text: 'Are you sure you want to delete this post? This action cannot be undone.', + }) + if (isCanceled) { + return; + } + + if (!confirm('Are you sure you want to delete this post forever?')) { + return; + } + + //Emit the delete event with the original post + emit('delete', posts.selectedItem.value) +} + +const setMeAsAuthor = () => { + apiCall(async () => { + const { first, last } = await getProfile<{first?:string, last?:string, email:string}>(); + v$.value.author.$model = `${first} ${last}` + }) +} + +const onEditorLoad = async (editor : any) =>{ + + //Get the initial content + const postContent = await content.getSelectedPostContent(); + + //Set the initial content + if(!isNil(postContent)){ + onContentChanged(postContent); + editor.setData(postContent); + } +} + +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/Posts/PostTable.vue b/front-end/src/views/Blog/components/Posts/PostTable.vue new file mode 100644 index 0000000..e5e45f2 --- /dev/null +++ b/front-end/src/views/Blog/components/Posts/PostTable.vue @@ -0,0 +1,62 @@ +<template> + <thead> + <tr> + <th>Title</th> + <th>Id</th> + <th>Date</th> + <th>Author</th> + <th>Summary</th> + <th></th> + </tr> + </thead> + <tbody> + <tr v-for="post in posts" :key="post.id" class="table-row"> + <td> + {{ post.title }} + </td> + <td> + {{ getPostId(post) }} + </td> + <td> + {{ getDateString(post.date) }} + </td> + <td> + {{ post.author }} + </td> + <td> + {{ getSummaryString(post.summary) }} + </td> + <td class="w-20"> + <button class="btn xs no-border" @click="copy(post.id)"> + <fa-icon icon="copy" /> + </button> + <button class="btn xs no-border" @click="openEdit(post)"> + <fa-icon icon="pencil" /> + </button> + </td> + </tr> + </tbody> +</template> + +<script setup lang="ts"> +import { toRefs } from 'vue'; +import { filter as _filter, truncate } from 'lodash'; +import { useClipboard } from '@vueuse/core'; +import { PostMeta } from '@vnuge/cmnext-admin'; + +const emit = defineEmits(['reload', 'open-edit']) + +const props = defineProps<{ + posts: PostMeta[], +}>() + +const { posts } = toRefs(props) + +const { copy } = useClipboard() + +const openEdit = async (post: PostMeta) => emit('open-edit', post) + +const getDateString = (time?: number) => new Date((time || 0) * 1000).toLocaleString(); +const getSummaryString = (summary?: string) => truncate(summary || '', { length: 40 }) +const getPostId = (post: PostMeta) => truncate(post.id || '', { length: 20 }) +</script>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue b/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue new file mode 100644 index 0000000..79b21cf --- /dev/null +++ b/front-end/src/views/Blog/components/podcast-helpers/EpisodeAdder.vue @@ -0,0 +1,164 @@ +<template> + <div id="podcast-upload-form"> + + <div class="ml-auto w-fit"> + <div class=""> + <button class="btn sm" @click="setIsOpen(true)">Add enclosure</button> + </div> + </div> + + <Dialog id="enclosure-dialog" :open="isOpen" @close="setIsOpen" class="relative z-50"> + <div class="fixed inset-0 bg-black/30" aria-hidden="true" /> + + <div class="fixed inset-0 flex justify-center pt-[8rem]"> + <DialogPanel class="dialog"> + <div class=""> + <DialogTitle>Set feed enclosure</DialogTitle> + <DialogDescription> + You may set a podcast episode or other rss enclosure from + content stored in the cms. + </DialogDescription> + <div class="my-3 ml-auto w-fit"> + <Popover class="relative"> + <PopoverButton class="btn"> + Add media + <fa-icon class="ml-2" icon="photo-film" /> + </PopoverButton> + <PopoverPanel class="absolute right-0 z-10 top-10"> + <div class="md-pannel"> + <div class=""> + Search for content by its id or file name. + </div> + <ContentSearch :blog="$props.blog" @selected="onContentSelected"/> + </div> + </PopoverPanel> + </Popover> + </div> + <dynamic-form + class="" + id="enclosure-form" + :form="schema" + :validator="v$" + @submit="onFormSubmit" + @cancel="onCancel" + /> + <div class="mt-4 ml-auto w-fit"> + <div class="button-group"> + <button class="btn sm primary" @click="onFormSubmit">Submit</button> + <button class="btn sm" @click="onCancel">Cancel</button> + </div> + </div> + </div> + </DialogPanel> + </div> + </Dialog> + </div> +</template> +<script setup lang="ts"> + +import { ref, reactive } from 'vue'; +import { BlogState } from '../../blog-api'; +import { PodcastEntity, getPodcastForm } from './podcast-form' +import { + Dialog, + DialogPanel, + DialogTitle, + DialogDescription, + PopoverButton, + PopoverPanel, + Popover, +} from '@headlessui/vue' +import ContentSearch from '../ContentSearch.vue' +import { apiCall, debugLog } from '@vnuge/vnlib.browser'; +import { ContentMeta } from '@vnuge/cmnext-admin'; + +const emit = defineEmits(['submit']) + +const props = defineProps<{ + blog: BlogState, +}>() + +const isOpen = ref(false) +const { getPublicUrl } = props.blog.content; +const { schema, setEnclosureContent, getValidator, exportProperties } = getPodcastForm() + +const buffer = reactive<PodcastEntity>({} as PodcastEntity) + +const { v$, validate } = getValidator(buffer) + +const setIsOpen = (value: boolean) => isOpen.value = value + +const onFormSubmit = async () =>{ + //Validate the form + if(! await validate()){ + return + } + + //get the enclosure properties to add to the xml + const props = exportProperties(buffer) + debugLog(props); + emit('submit', props) + setIsOpen(false) +} + +const onCancel = () =>{ + setIsOpen(false) +} + +const onContentSelected = (content: ContentMeta) =>{ + apiCall(async () =>{ + //Get the content link from the server + const url = await getPublicUrl(content) + + //set the form content + setEnclosureContent(buffer, content, `/${url}`) + }) +} + +</script> + +<style lang="scss"> + +#enclosure-dialog{ + + .dialog{ + @apply w-full max-w-3xl px-8 pb-8 pt-4 mx-auto mb-auto border rounded shadow-md; + @apply bg-white dark:bg-dark-700 dark:text-gray-300 dark:border-dark-500; + } + + .dynamic-form.input-group{ + @apply grid grid-cols-2 gap-4; + } + + .dynamic-form.input-container{ + @apply flex flex-col; + } + + .dynamic-form.field-description{ + @apply text-sm text-gray-500 dark:text-gray-400 px-2; + } + + .dynamic-form.input-label{ + @apply text-sm font-semibold text-gray-700 dark:text-gray-100 ml-1 mb-1; + } + + .dynamic-form.dynamic-input.input{ + @apply py-1.5 bg-transparent; + + &:disabled{ + @apply bg-gray-100 dark:bg-transparent dark:border-transparent; + } + + &:focus{ + @apply border-primary-500; + } + } + + .dirty.dynamic-form.input-container{ + &.data-invalid .dynamic-form.dynamic-input.input{ + @apply border-red-500; + } + } +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts b/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts new file mode 100644 index 0000000..ab8ad8a --- /dev/null +++ b/front-end/src/views/Blog/components/podcast-helpers/podcast-form.ts @@ -0,0 +1,174 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { computed, Ref } from 'vue'; +import { helpers, required, maxLength, alphaNum, numeric } from "@vuelidate/validators" +import useVuelidate from "@vuelidate/core" +import { MaybeRef } from '@vueuse/core'; +import { useVuelidateWrapper } from '@vnuge/vnlib.browser'; +import { ContentMeta, FeedProperty } from '@vnuge/cmnext-admin'; + +export interface EnclosureEntity{ + fileId: string; + contentUrl: string; + contentLength: number; + contentType: string; +} + +export interface PodcastEntity extends EnclosureEntity{ + episodeType: string; + duration: number; +} + +export const getPodcastForm = (editMode?: Ref<boolean>) => { + const schema = computed(() => { + return { + fields: [ + { + id: 'episode-type', + type: 'text', + label: 'Episode Type', + name: 'episodeType', + placeholder: '', + description: 'The itunes episode type, typically "full" or "trailer"', + }, + { + id: 'episode-duration', + type: 'text', + label: 'Duration', + name: 'duration', + placeholder: '', + description: 'The duration in seconds for the episode', + }, + { + id: 'ep-content-id', + type: 'text', + label: 'File Id', + name: 'fileId', + placeholder: '', + description: 'The file id of the episode already in the channel', + disabled: true, + }, + { + id: 'content-url', + type: 'text', + label: 'Content url', + name: 'contentUrl', + placeholder: '', + description: 'This the relative url to the episode content file', + disabled: true, + }, + { + id: 'content-length', + type: 'text', + label: 'Content length', + name: 'contentLength', + placeholder: '', + description: 'This the length in bytes of the episode content file', + disabled: true, + }, + { + id: 'content-type', + type: 'text', + label: 'The MIME content type', + name: 'contentType', + placeholder: '', + description: 'The MIME content type for the episode content file', + disabled: true, + } + ] + } + }); + + + const alphaNumSlash = helpers.regex(/^[a-zA-Z0-9\/]*$/); + + const rules = { + fileId: { + required:helpers.withMessage('The file id is required', required), + maxLength: helpers.withMessage('The file id must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The file id must be alpha numeric', alphaNum) + }, + episodeType: { + required: helpers.withMessage('The episode type is required', required), + maxLength: helpers.withMessage('The episode type must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The episode type must be alpha numeric', alphaNum) + }, + duration: { + required: helpers.withMessage('The duration is required', required), + numeric: helpers.withMessage('The duration must be a number', numeric) + }, + contentUrl: { + required: helpers.withMessage('The content url is required', required), + maxLength: helpers.withMessage('The content url must be less than 256 characters', maxLength(256)) + }, + contentLength: { + required: helpers.withMessage('The content length is required', required), + numeric: helpers.withMessage('The content length must be a number', numeric) + }, + contentType: { + required: helpers.withMessage('The content type is required', required), + maxLength: helpers.withMessage('The content type must be less than 64 characters', maxLength(64)), + alphaNumeric: helpers.withMessage('The content type must be in MIME format', alphaNumSlash) + } + } + + const getValidator = <T extends PodcastEntity>(buffer: MaybeRef<T>) => { + const v$ = useVuelidate(rules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + + return { v$, validate, reset: v$.value.$reset }; + } + + const setEnclosureContent = (enclosure: EnclosureEntity, content: ContentMeta, url: string) => { + enclosure.fileId = content.id; + enclosure.contentLength = content.length + enclosure.contentType = content.content_type; + enclosure.contentUrl = url; + } + + const exportProperties = (podcast: PodcastEntity) : FeedProperty[] => { + return [ + { + name: 'episodeType', + namespace: 'itunes', + value: podcast.episodeType + }, + { + name: 'duration', + namespace: 'itunes', + value: podcast.duration?.toString() + }, + //Setup the enclosure + { + name:"enclosure", + attributes:{ + url: podcast.contentUrl, + length: podcast.contentLength?.toString(), + type: podcast.contentType + }, + } + ] + } + + return { + schema, + rules, + getValidator, + setEnclosureContent, + exportProperties + }; +} + diff --git a/front-end/src/views/Blog/form-helpers/channels.ts b/front-end/src/views/Blog/form-helpers/channels.ts new file mode 100644 index 0000000..cd33a20 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/channels.ts @@ -0,0 +1,227 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { MaybeRef, computed, watch, Ref } from 'vue' +import { helpers, required, maxLength, numeric } from "@vuelidate/validators" +import { useVuelidateWrapper } from '@vnuge/vnlib.browser'; +import { BlogChannel, ChannelFeed } from '@vnuge/cmnext-admin'; +import useVuelidate from "@vuelidate/core" + +export const getChannelForm = (editMode?: Ref<boolean>) => { + const channelSchema = computed(() => { + return { + fields: [ + { + id: 'channel-name', + type: 'text', + label: 'Channel Name', + name: 'name', + placeholder: 'Enter the name of the channel', + description: 'A simple human readable name for the channel' + }, + { + id: 'channel-path', + type: 'text', + label: 'Root Path', + name: 'path', + placeholder: 'Enter the root path to the channel', + description: editMode?.value ? 'You may not edit the channel directory' : 'The path in your bucket to the working directory for the channel', + disabled: editMode?.value + }, + { + id: 'channel-index', + type: 'text', + label: 'Index File', + name: 'index', + placeholder: 'Enter the index file for the channel', + description: editMode?.value ? + 'You may not edit the index file path' + : 'The name or path of the post index file, stored under the root directory of the channel', + disabled: editMode?.value + }, + { + id: 'channel-content-dir', + type: 'text', + label: 'Content Directory', + name: 'content', + placeholder: 'Enter the content directory for the channel', + description: editMode?.value ? + 'You may not edit the content directory path' + : 'The name or path of the content directory, stored under the root directory of the channel', + disabled: editMode?.value + }, + { + id: 'index-file-example', + type: 'text', + label: 'Index Path', + name: 'example', + placeholder: 'Your index file path', + description: 'This is the location within your bucket where the index file will be stored', + disabled: true, + } + ] + } + }); + + const feedSchema = { + fields: [ + { + id: 'channel-feed-url', + type: 'text', + label: 'Publish Url', + name: 'url', + placeholder: 'Enter the feed url for the channel', + description: 'The rss syndication url for your blog channel, the http url your blog resides at.' + }, + { + id: 'channel-feed-path', + type: 'text', + label: 'Feed File', + name: 'path', + placeholder: 'feed.xml', + description: 'The path to the feed xml file within the channel directory' + }, + { + id: 'channel-feed-image', + type: 'text', + label: 'Image Url', + name: 'image', + placeholder: 'Enter the url for the default feed image', + description: 'The full http url to the default feed image' + }, + { + id: 'channel-feed-author', + type: 'text', + label: 'Feed Author', + name: 'author', + placeholder: 'Your name', + description: 'The author name for the feed' + }, + { + id: 'channel-feed-contact', + type: 'text', + label: 'Feed Contact', + name: 'contact', + placeholder: 'Your contact email address', + description: 'The webmaster contact email address' + }, + { + id: 'channel-feed-max-items', + type: 'number', + label: 'Feed Max Items', + name: 'maxItems', + placeholder: 'Enter the feed max items for the channel', + description: 'The maximum number of posts to publish in the feed' + }, + { + id: 'channel-feed-description', + type: 'textarea', + label: 'Feed Description', + name: 'description', + placeholder: 'Enter the feed description for the channel', + } + ] + } + + const alphaNumSpace = helpers.regex(/^[a-zA-Z0-9 ]*$/); + const httpUrl = helpers.regex(/^(http|https):\/\/[^ "]+$/); + + const channelRules = { + name: { + required: helpers.withMessage('Channel name is required', required), + maxlength: helpers.withMessage('Channel name must be less than 50 characters', maxLength(50)), + alphaNumSpace: helpers.withMessage('Channel name must be alphanumeric', alphaNumSpace), + }, + path: { + required: helpers.withMessage('Channel path is required', required), + maxlength: helpers.withMessage('Channel path must be less than 50 characters', maxLength(50)), + }, + index: { + required: helpers.withMessage('Channel index is required', required), + maxlength: helpers.withMessage('Channel index must be less than 50 characters', maxLength(50)), + }, + content: { + required: helpers.withMessage('Channel content directory is required', required), + maxlength: helpers.withMessage('Channel content directory must be less than 50 characters', maxLength(50)), + }, + example: {} + } + + const feedRules = { + url: { + required: helpers.withMessage('Channel feed url is required', required), + maxlength: helpers.withMessage('Channel feed url must be less than 100 characters', maxLength(100)), + url: helpers.withMessage('Channel feed url must be a valid url', httpUrl), + }, + path: { + required: helpers.withMessage('Channel feed path is required', required), + maxlength: helpers.withMessage('Channel feed path must be less than 50 characters', maxLength(50)), + }, + image: { + maxlength: helpers.withMessage('Channel feed image must be less than 200 characters', maxLength(200)), + }, + contact: { + maxlength: helpers.withMessage('Channel feed contact must be less than 50 characters', maxLength(50)), + }, + description: { + alphaNumSpace: helpers.withMessage('Channel feed description must be alphanumeric', alphaNumSpace), + maxlength: helpers.withMessage('Channel feed description must be less than 50 characters', maxLength(200)), + }, + maxItems: { + numeric: helpers.withMessage('Channel feed max items must be a number', numeric), + }, + author: { + alphaNumSpace: helpers.withMessage('Channel feed author must be alphanumeric', alphaNumSpace), + maxlength: helpers.withMessage('Channel feed author must be less than 50 characters', maxLength(50)), + } + } + + const getChannelValidator = <T extends BlogChannel>(buffer: MaybeRef<T | undefined>) => { + + const v$ = useVuelidate(channelRules, buffer, { $lazy: true, $autoDirty: true }); + + const updateExample = () => { + if (!v$.value.path.$model || !v$.value.index.$model) { + v$.value.example.$model = ''; + return; + } + //Update the example path + v$.value.example.$model = `${v$.value.path.$model}/${v$.value.index.$model}`; + } + + watch(v$, updateExample); + + updateExample(); + + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + const getFeedValidator = <T extends ChannelFeed>(buffer: MaybeRef<T | undefined>) => { + const v$ = useVuelidate(feedRules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + return { + channelSchema, + feedSchema, + channelRules, + feedRules, + getChannelValidator, + getFeedValidator + }; +} + diff --git a/front-end/src/views/Blog/form-helpers/index.ts b/front-end/src/views/Blog/form-helpers/index.ts new file mode 100644 index 0000000..d3df0f7 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/index.ts @@ -0,0 +1,17 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +export * from './channels' +export * from './posts'
\ No newline at end of file diff --git a/front-end/src/views/Blog/form-helpers/posts.ts b/front-end/src/views/Blog/form-helpers/posts.ts new file mode 100644 index 0000000..da805e7 --- /dev/null +++ b/front-end/src/views/Blog/form-helpers/posts.ts @@ -0,0 +1,116 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { MaybeRef, computed } from "vue"; +import { useVuelidateWrapper } from "@vnuge/vnlib.browser" +import { PostMeta } from '@vnuge/cmnext-admin' +import { helpers, required, maxLength } from "@vuelidate/validators" +import useVuelidate from "@vuelidate/core" + +export const getPostForm = () => { + + const schema = computed(() => { + return { + fields: [ + { + id: 'post-title', + type: 'text', + label: 'Post Title', + name: 'title', + placeholder: 'Enter the title of the post', + description: 'A simple human readable title for the post' + }, + { + id: 'post-author', + type: 'text', + label: 'Post Author', + name: 'author', + placeholder: 'Enter the author of the post', + description: 'The author of the post' + }, + { + id: 'post-tags', + type: 'text', + label: 'Post Tags', + name: 'tags', + placeholder: 'Enter the tags for the post', + description: 'A comma separated list of tags for the post' + }, + { + id: 'post-image', + type: 'text', + label: 'Post Image', + name: 'image', + placeholder: 'Enter the image url for the post', + description: 'The full http url to the post image' + }, + { + id: 'post-summary', + type: 'textarea', + label: 'Post Summary', + name: 'summary', + placeholder: 'Enter the summary of the post', + description: 'A short summary of the post, also the description for the rss feed' + }, + { + id: 'existing-post-id', + type: 'text', + label: 'Post Id', + name: 'id', + placeholder: '', + description: 'The id of the post, this cannot be changed', + disabled: true, + } + ] + } + }); + + const alphaNumSpace = helpers.regex(/^[a-zA-Z0-9 ]*$/); + const httpUrl = helpers.regex(/^(http|https):\/\/[^ "]+$/); + + const rules = { + title: { + required: helpers.withMessage('Post title is required', required), + maxlength: helpers.withMessage('Post title must be less than 50 characters', maxLength(50)), + alphaNumSpace: helpers.withMessage('Post title must be alphanumeric', alphaNumSpace), + }, + summary: { + required: helpers.withMessage('Post summary is required', required), + maxlength: helpers.withMessage('Post summary must be less than 50 characters', maxLength(200)), + }, + author: { + required: helpers.withMessage('Post author is required', required), + maxlength: helpers.withMessage('Post author must be less than 50 characters', maxLength(50)), + }, + tags: {}, + image: { + maxlength: helpers.withMessage('Post image must be less than 200 characters', maxLength(200)), + httpUrl: helpers.withMessage('Post image must be a valid http url', httpUrl), + }, + content: { + required: helpers.withMessage('Post content is required', required), + maxLength: maxLength(50000), + }, + id: {} + } + + const getValidator = <T extends PostMeta>(buffer: MaybeRef<T | undefined>) => { + const v$ = useVuelidate(rules, buffer, { $lazy: true, $autoDirty: true }); + const { validate } = useVuelidateWrapper(v$); + return { v$, validate, reset: v$.value.$reset }; + } + + return { schema, rules, getValidator }; +}
\ No newline at end of file diff --git a/front-end/src/views/Blog/index.vue b/front-end/src/views/Blog/index.vue new file mode 100644 index 0000000..6bfcb6e --- /dev/null +++ b/front-end/src/views/Blog/index.vue @@ -0,0 +1,324 @@ +<template> + <div class="container mx-auto mt-10 mb-[10rem]"> + <div id="blog-admin-template" class=""> + + <TabGroup vertical :selected-index="tabId" @change="onTabChange"> + <div class="menu"> + <TabList> + <div class="inline-flex items-center justify-center w-16 h-16"> + <span class="username-box"> + {{ firstLetter }} + </span> + </div> + + <div class="border-t border-gray-100 dark:border-dark-500"> + <div class="px-2"> + + <Tab v-slot="{ selected }" as="div" class="py-4"> + <div class="t group menu-item" :class="{'active':selected}"> + + <fa-icon icon="bullhorn" size="lg" /> + + <span class="opacity-0 tooltip group-hover:opacity-100"> + Channel + </span> + </div> + </Tab> + + <ul class="flex flex-col pt-4 space-y-1 border-t border-gray-100 dark:border-dark-500"> + <Tab v-slot="{ selected }" as="li"> + <div class="group menu-item" :class="{'active':selected}"> + + <fa-icon icon="comment" size="xl" /> + + <span class="opacity-0 tooltip group-hover:opacity-100"> + Posts + </span> + </div> + </Tab> + <Tab v-slot="{ selected }" as="li"> + <div class="group menu-item" :class="{'active':selected}"> + + <fa-icon icon="folder-open" size="lg" /> + + <span class="opacity-0 tooltip group-hover:opacity-100"> + Content + </span> + </div> + </Tab> + + </ul> + </div> + </div> + </TabList> + </div> + + <TabPanels class="tab-container"> + <div class="flex flex-row h-12 px-4 pb-2"> + + <div class="inline-flex flex-row gap-3"> + <div class="my-auto"> + <fa-icon icon="bullhorn" /> + </div> + + <select id="channel-select" class="" v-model="channel"> + <option value="">Select Channel</option> + <option v-for="c in channels.items.value" :value="c.id"> + {{ c.name }} + </option> + </select> + </div> + + <div class="flex flex-row w-full max-w-md gap-4 ml-auto mr-4 filter"> + <div class="my-auto">Filter</div> + <input class="w-full rounded input primary" v-model="search"/> + </div> + + <div class="flex flex-row py-2 mr-auto"> + <Switch v-model="lastModified" + :class="lastModified ? 'bg-primary-500' : 'bg-gray-300 dark:bg-dark-500'" + class="relative inline-flex items-center w-10 h-5 my-auto duration-75 rounded-full"> + <span class="sr-only">Last modified</span> + <span :class="lastModified ? 'translate-x-6' : 'translate-x-1'" + class="inline-block w-3 h-3 transition transform bg-white rounded-full" /> + </Switch> + <div class="my-auto ml-3"> + Last Modifed + </div> + + </div> + </div> + + <TabPanel> + <Channels :blog="blogState" /> + </TabPanel> + + <TabPanel> + <Posts :blog="blogState" /> + </TabPanel> + + <TabPanel> + <Content :blog="blogState" /> + </TabPanel> + + </TabPanels> + </TabGroup> + </div> + </div> +</template> + +<script setup lang="ts"> +import { computed } from 'vue'; +import { useScriptTag } from '@vueuse/core'; +import { useRouteQuery } from '@vueuse/router'; +import { TabGroup, TabList, Tab, TabPanels, TabPanel, Switch } from '@headlessui/vue' +import { first } from 'lodash'; +import { usePageGuard, useUser, useTitle } from '@vnuge/vnlib.browser'; +import { createBlogContext, useComputedChannels, useComputedPosts, useComputedContent, SortType } from '@vnuge/cmnext-admin'; +import { BlogState } from './blog-api'; +import Channels from './components/Channels.vue'; +import Posts from './components/Posts.vue'; +import Content from './components/Content.vue'; + +//Protect page +usePageGuard(); +useTitle('CMNext Admin') + +//Load scripts +const ckEditorTag = useScriptTag("https://cdn.ckeditor.com/ckeditor5/35.4.0/super-build/ckeditor.js") +//Store the wait result on the window for the editor script to wait +window.editorLoadResult = ckEditorTag.load(true); + +const { userName, getProfile } = useUser() + +//Load user profile and forget if not set +if(!userName.value){ + getProfile() +} + +const firstLetter = computed(() => first(userName.value)) + +const tabIdQ = useRouteQuery<string>('tabid', '', { mode: 'push' }) + +const context = createBlogContext({ + channelUrl: '/blog/channels', + postUrl: '/blog/posts', + contentUrl: '/blog/content' +}) + +const { search, sort, channel } = context.getQuery(); + +const channels = useComputedChannels(context) +const posts = useComputedPosts(context) +const content = useComputedContent(context) + +const blogState = { channels, posts, content } as BlogState + +//Map queries to their respective computed values +const tabId = computed(() => tabIdQ.value ? parseInt(tabIdQ.value) : 0); +const lastModified = computed({ + get :() => sort.value === SortType.ModifiedTime, + set: (value:boolean) => { + sort.value = value ? SortType.ModifiedTime : SortType.CreatedTime + } +}) + +const onTabChange = (id:number) => tabIdQ.value = id.toString(10) + +</script> + +<style lang="scss"> + +#blog-admin-template{ + @apply flex flex-row flex-auto min-h-[50rem] border rounded-sm max-w-[82rem] mx-auto; + @apply dark:border-dark-600 dark:text-gray-300 border-gray-200; + + .username-box{ + @apply grid w-10 h-10 text-sm rounded-lg place-content-center; + @apply text-gray-600 bg-gray-100 dark:text-gray-300 dark:bg-dark-600; + } + + .menu-item{ + @apply relative flex justify-center rounded px-2 py-2 cursor-pointer; + @apply text-gray-500 hover:bg-gray-50 hover:text-gray-700 dark:hover:bg-dark-700 dark:hover:text-gray-300; + + &.active{ + @apply text-primary-600; + } + + .tooltip{ + @apply absolute start-full -translate-y-1/2 top-1/2 ms-4 rounded px-2 py-1.5 text-xs font-medium; + @apply text-white bg-gray-900 dark:bg-dark-600; + } + } + + .menu{ + @apply flex flex-col justify-between w-16 border-e; + @apply bg-white dark:bg-dark-800 dark:border-dark-500; + } + + #channel-select{ + @apply w-full p-1 px-2 border rounded-sm sm:text-sm min-w-[13rem]; + @apply border-gray-300 text-gray-700 bg-white; + @apply dark:bg-dark-800 dark:border-dark-500 focus:dark:border-dark-400 hover:dark:border-dark-400 dark:text-inherit; + + option{ + @apply text-base; + } + } + + .tab-container{ + @apply flex-1 py-4 rounded-r-sm dark:bg-dark-800 bg-white text-gray-700 dark:text-inherit; + } + + // Rules for dynamic forms in edit panes + .dynamic-form.form{ + @apply w-full mt-4 md:px-12; + + .dynamic-form.input-group{ + @apply grid grid-flow-row grid-cols-2; + } + + .dynamic-form.input-group{ + @apply gap-x-16; + + .dynamic-form.input-container{ + + .dynamic-form.dynamic-input{ + @apply border rounded-sm p-2 bg-transparent w-full dark:border-dark-600; + @apply dark:bg-dark-800 focus:border-primary-500; + + &.input-textarea{ + @apply h-40 outline-none; + } + + &::placeholder{ + @apply dark:text-gray-500; + } + + &:disabled{ + @apply text-rose-400 border-transparent; + } + } + + &.dirty.data-invalid .dynamic-form.dynamic-input{ + @apply border-red-500 focus:border-red-500; + } + + .dynamic-form.field-description{ + @apply pt-1 p-2 pb-4 text-sm; + } + + } + + .dynamic-form.input-label{ + @apply col-span-2 text-right m-auto mr-2; + } + } + } + + table.edit-table { + @apply w-full divide-y-2 divide-gray-200 bg-white text-sm dark:divide-dark-500 dark:bg-dark-800; + + thead{ + @apply text-left text-lg; + } + + tbody{ + @apply divide-y divide-gray-200 dark:divide-dark-500; + } + + thead th, + tr td{ + @apply whitespace-nowrap px-4 py-2 font-medium; + } + } + + .ck.ck-editor{ + @apply border dark:border-coolGray-600; + } + + .ck-editor .ck-content, + .ck-editor .ck-source-editing-area{ + @apply min-h-[32rem] resize-y dark:bg-dark-800; + + a { + @apply text-blue-500; + } + + p{ + @apply my-2; + } + + pre{ + @apply p-2 dark:text-gray-200; + } + + h1, h2{ + @apply border-b pb-3 mb-4; + } + } + + .ck-source-editing-area textarea{ + @apply dark:bg-transparent; + } + + .ck.ck-toolbar, + .ck.ck-reset + { + @apply dark:bg-dark-800 dark:text-gray-300; + + .ck-button, + .ck-dropdown + { + @apply dark:text-gray-300; + + &:hover, + &.ck-on + { + @apply dark:bg-dark-600; + } + } + } +} +</style>
\ No newline at end of file diff --git a/front-end/src/views/Login/components/Social.vue b/front-end/src/views/Login/components/Social.vue new file mode 100644 index 0000000..34c5a1e --- /dev/null +++ b/front-end/src/views/Login/components/Social.vue @@ -0,0 +1,57 @@ +<template> + + <form class="w-full" @submit.prevent="SocalLogin('/login/social/github')"> + <button type="submit" class="btn social-button" :disabled="waiting"> + <fa-icon :icon="['fab','github']" size="xl" /> + Login with Github + </button> + </form> + + <form class="mt-4" @submit.prevent="SocalLogin('/login/social/discord')"> + <button type="submit" class="btn social-button" :disabled="waiting"> + <fa-icon :icon="['fab','discord']" size="xl" /> + Login with Discord + </button> + </form> + + <form v-if="auth0Enabled" class="mt-4" @submit.prevent="SocalLogin('/login/social/auth0')"> + <button type="submit" class="btn social-button" :disabled="waiting"> + <fa-icon :icon="['fa','key']" size="xl" /> + Login with Auth0 + </button> + </form> + +</template> + +<script setup lang="ts"> +import { apiCall, useWait, useSession, useSessionUtils, WebMessage } from '@vnuge/vnlib.browser' + +//auth0 enabled flag from env +const auth0Enabled = import.meta.env.VITE_ENABLE_AUTH0 == 'true'; + +const { waiting } = useWait() +const { browserId, publicKey } = useSession() +const { KeyStore } = useSessionUtils() + +const SocalLogin = async (url:string) => { + await apiCall(async ({ axios }) => { + const { data } = await axios.put<WebMessage<string>>(url, { + browser_id: browserId.value, + public_key: publicKey.value + }) + + const encDat = data.getResultOrThrow() + // Decrypt the result which should be a redirect url + const result = await KeyStore.decryptDataAsync(encDat) + // get utf8 text + const text = new TextDecoder('utf-8').decode(result) + // Recover url + const redirect = new URL(text) + // Force https + redirect.protocol = 'https:' + // redirect to the url + window.location.href = redirect.href + }) +} + +</script>
\ No newline at end of file diff --git a/front-end/src/views/Login/components/Totp.vue b/front-end/src/views/Login/components/Totp.vue new file mode 100644 index 0000000..50a5be3 --- /dev/null +++ b/front-end/src/views/Login/components/Totp.vue @@ -0,0 +1,65 @@ +<template> + <div id="totp-login-form"> + <h5>Enter your TOTP code</h5> + <div class="flex flex-col h-32"> + <div class="h-8 mx-auto"> + <fa-icon v-if="waiting" class="animate-spin" size="xl" icon="spinner"/> + </div> + <div class="mx-auto mt-4"> + <VOtpInput + class="otp-input" + input-type="letter-numeric" + :is-disabled="waiting" + separator="" + input-classes="primary input rounded" + :num-inputs="6" + value="" + @on-change="onInput" + @on-complete="SubimitTotp" + /> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { useMessage, useWait } from '@vnuge/vnlib.browser'; +import { toSafeInteger } from 'lodash'; +import VOtpInput from "vue3-otp-input"; + +const emit = defineEmits(['submit']) + +const { waiting } = useWait(); +const { onInput } = useMessage(); + +const SubimitTotp = async (code : string) => { + + //If a request is still pending, do nothing + if (waiting.value) { + return + } + + //Submit a mfa upgrade result + emit('submit', { + code: toSafeInteger(code) + }) +} + + +</script> + +<style lang="scss"> + +#totp-login-form { + .otp-input { + @apply rounded-sm gap-2; + + input { + @apply w-12 h-12 p-3 text-center text-2xl; + appearance: none; + -webkit-appearance: none; + } + } +} + +</style>
\ No newline at end of file diff --git a/front-end/src/views/Login/components/UserPass.vue b/front-end/src/views/Login/components/UserPass.vue new file mode 100644 index 0000000..e218cb8 --- /dev/null +++ b/front-end/src/views/Login/components/UserPass.vue @@ -0,0 +1,92 @@ +<template> + <div class=""> + <h3>Login</h3> + <form id="user-pass-submit-form" method="post" action="/login" @submit.prevent="SubmitLogin"> + <fieldset class="" :disabled="waiting" > + <div> + <div class="float-label"> + <input + id="username" + v-model="v$.username.$model" + type="email" + class="w-full primary input" + placeholder="Email" + :class="{ 'data-invalid': v$.username.$invalid }" + @input="onInput" + > + <label for="username">Email</label> + </div> + </div> + <div class="py-3"> + <div class="mb-2 float-label"> + <input + id="password" + v-model="v$.password.$model" + type="password" + class="w-full primary input" + placeholder="Password" + :class="{ 'data-invalid': v$.password.$invalid }" + @input="onInput" + > + <label for="password">Password</label> + </div> + </div> + </fieldset> + <button type="submit" form="user-pass-submit-form" class="btn primary" :disabled="waiting"> + <!-- Display spinner if waiting, otherwise the sign-in icon --> + <fa-icon :class="{'animate-spin':waiting}" :icon="waiting ? 'spinner' : 'sign-in-alt'"/> + Log-in + </button> + </form> + <div class="flex flex-row justify-between gap-3 pt-3 pb-2 form-links"> + <router-link to="/pwreset"> + Forgot password + </router-link> + <router-link to="/register"> + Register a new account + </router-link> + </div> + </div> +</template> + +<script setup lang="ts"> +import { reactive } from 'vue' +import useVuelidate from '@vuelidate/core' +import { required, maxLength, minLength, email, helpers } from '@vuelidate/validators' +import { useMessage, useVuelidateWrapper, useWait } from '@vnuge/vnlib.browser' + +const emit = defineEmits(['login']) + +const { onInput } = useMessage(); +const { waiting } = useWait(); + +const vState = reactive({ username: '', password: '' }) + +const rules = { + username: { + required: helpers.withMessage('Email cannot be empty', required), + email: helpers.withMessage('Your email address is not valid', email), + maxLength: helpers.withMessage('Email address must be less than 50 characters', maxLength(50)) + }, + password: { + required: helpers.withMessage('Password cannot be empty', required), + minLength: helpers.withMessage('Password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('Password must have less than 128 characters', maxLength(128)) + } +} + +const v$ = useVuelidate(rules, vState) +const { validate } = useVuelidateWrapper(v$); + +const SubmitLogin = async () => { + + // If the form is not valid set the error message + if (!await validate()) { + return + } + + //Emit login and pass the username and password + emit('login', { username: v$.value.username.$model, password: v$.value.password.$model }); +} + +</script>
\ No newline at end of file diff --git a/front-end/src/views/Login/index.vue b/front-end/src/views/Login/index.vue new file mode 100644 index 0000000..f3a3f59 --- /dev/null +++ b/front-end/src/views/Login/index.vue @@ -0,0 +1,182 @@ +<template> + <div id="login-template" class="app-component-entry"> + <div class="login-container"> + + <div v-if="showTotp"> + <Totp @submit="totpSubmit" /> + </div> + + <div v-else-if="!loggedIn"> + <UserPass @login="submitLogin" /> + </div> + + <div v-else> + <h3>Logout</h3> + <p class="mt-3 mb-5 text-lg"> + You are currently logged-in. + </p> + <div class=""> + <button form="user-pass-submit-form" class="btn primary" @click="submitLogout" :disabled="waiting"> + <!-- Display spinner if waiting, otherwise the sign-in icon --> + <fa-icon :class="{'animate-spin':waiting}" :icon="waiting ? 'spinner' : 'sign-in-alt'"/> + Log-out + </button> + </div> + </div> + + <div v-if="!(loggedIn || showTotp)" class="w-full mt-6"> + + <Social /> + + <!-- pki button, forward to the pki route --> + <div v-if="pkiEnabled" class="mt-4"> + <router-link to="/login/pki"> + <button type="submit" class="btn red social-button" :disabled="waiting"> + <fa-icon :icon="['fa','certificate']" size="xl" /> + Login with PKI Credential + </button> + </router-link> + </div> + </div> + + </div> + </div> +</template> + +<script setup lang="ts"> +import { computed, ref } from 'vue' +import Totp from './components/Totp.vue' +import UserPass from './components/UserPass.vue' +import Social from './components/Social.vue' +import { apiCall, useMessage, useWait, useUser, useSession, useLastPage, useTitle, debugLog } from '@vnuge/vnlib.browser' +import { useMfaLogin, totpMfaProcessor, IMfaFlowContinuiation, MfaMethod } from '@vnuge/vnlib.browser/dist/mfa' +import { useTimeoutFn } from '@vueuse/shared' +import { isNil } from 'lodash' + +useTitle('Login') + +//pki enabled flag from env +const pkiEnabled = !isNil(import.meta.env.VITE_PKI_ENDPOINT); + +const { waiting } = useWait() +const { setMessage } = useMessage() +const { logout } = useUser(); +const { loggedIn } = useSession() + +//Setup mfa login +const { login } = useMfaLogin([ + totpMfaProcessor() +]) + +//If logged in re-route to the last page the user +//was on but delayed to the session has time to be set +const { gotoLastPage } = useLastPage() +useTimeoutFn(() => loggedIn.value ? gotoLastPage() : null, 500) + +const mfaUpgrade = ref<IMfaFlowContinuiation>(); +const mfaTimer = ref<{stop:() => void}>(); +const showTotp = computed(() => mfaUpgrade.value?.type === MfaMethod.TOTP) + +const submitLogout = async () => { + //Submit logout request + await apiCall(async ({ toaster }) => { + // Attempt to login + await logout() + // Push a new toast message + toaster.general.success({ + id: 'logout-success', + title: 'Success', + text: 'You have been logged out', + duration: 5000 + }) + }) +} + +const submitLogin = async ({username, password} : { username: string, password:string }) => { + // Run login in an apicall wrapper + await apiCall(async ({ toaster }) => { + // Attempt to login + const response = await login(username, password) + + debugLog('Mfa-login',response) + + //Try to get response as a flow continuation + const mfa = response as IMfaFlowContinuiation + + // Response is a totp upgrade request + if (mfa.type === MfaMethod.TOTP) { + + //Store the upgrade message + mfaUpgrade.value = mfa; + + // Set timeout to reset the form when totp expires + mfaTimer.value = useTimeoutFn(() => { + + //Clear upgrade message + mfaUpgrade.value = undefined; + + setMessage('Your TOTP request has expired') + + }, mfa.expires! * 1000) + } + //If login without mfa was successful + else if (response.success) { + // Push a new toast message + toaster.general.success({ + title: 'Success', + text: 'You have been logged in', + }) + + return; + } + }) +} + +const totpSubmit = ({ code } : {code:number}) =>{ + apiCall(async ({ toaster }) =>{ + + if (!mfaUpgrade.value) + return; + + //Submit totp code + const res = await mfaUpgrade.value.submit({ code }) + res.getResultOrThrow() + + //Clear timer + mfaTimer.value?.stop() + + //Clear upgrade message + mfaUpgrade.value = undefined; + + // Push a new toast message + toaster.general.success({ + title: 'Success', + text: 'You have been logged in', + }) + }) +} + +</script> + +<style lang="scss"> +#login-template { + .login-container{ + @apply container max-w-sm w-full sm:mt-2 mt-8 mb-16 mx-auto lg:mt-16 px-6 py-4 flex flex-col; + @apply ease-linear duration-150 text-center; + @apply rounded-sm sm:bg-white sm:border shadow-sm border-gray-200 sm:dark:bg-dark-800 dark:border-dark-500; + } + + .login-container button{ + @apply w-full border py-2.5; + } + + button.social-button { + @apply flex flex-row justify-center gap-3 items-center; + } + + a { + @apply ease-in-out duration-100; + @apply hover:text-primary-600 dark:hover:text-primary-500; + } +} +</style> diff --git a/front-end/src/views/Login/pki/index.vue b/front-end/src/views/Login/pki/index.vue new file mode 100644 index 0000000..ae1a4a8 --- /dev/null +++ b/front-end/src/views/Login/pki/index.vue @@ -0,0 +1,80 @@ +<template> + <div id="pki-login-template" class="app-component-entry"> + <div class="container max-w-lg mx-auto mt-6 lg:mt-20"> + <div class="p-2 text-center bg-white border rounded shadow-md dark:border-dark-500 dark:bg-dark-800"> + + <h4>Enter your PKI-OTP</h4> + + <div class="p-3"> + <div class=""> + <textarea v-model="otp" class="w-full p-1 border rounded-sm input primary" rows="5"></textarea> + </div> + + <div class="flex justify-between mt-4"> + <div class="text-sm"> + <a class="link" target="_blank" href="https://github.com/VnUgE/Plugins.Essentials/tree/master/plugins/VNLib.Plugins.Essentials.Accounts"> + Goto OTP spec + <fa-icon icon="arrow-right" class="ml-1" /> + </a> + </div> + <div class="button-group"> + <RouterLink to="/login"> + <button class="btn">Back</button> + </RouterLink> + <button class="btn primary" @click.prevent="submit">Login</button> + </div> + </div> + + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { isEmpty } from 'lodash'; +import { apiCall, debugLog, useUser, useMessage } from '@vnuge/vnlib.browser'; +import { ITokenResponse } from '@vnuge/vnlib.browser/dist/session'; +import { ref } from 'vue' +import { decodeJwt } from 'jose' +import { useRouter } from 'vue-router'; + +const otp = ref('') + +const pkiEndpoint = import.meta.env.VITE_PKI_ENDPOINT + +const { prepareLogin } = useUser() +const { setMessage } = useMessage() +const { push } = useRouter() + +const submit = () =>{ + + apiCall(async ({ axios }) =>{ + if(isEmpty(otp.value)){ + setMessage('Please enter your OTP') + return + } + + //try to decode the jwt to confirm its form is valid + const jwt = decodeJwt(otp.value) + debugLog(jwt) + + //Prepare a login message + const loginMessage = prepareLogin() + + //Set the 'login' field to the otp + loginMessage.login = otp.value + + const { data } = await axios.post<ITokenResponse>(pkiEndpoint, loginMessage) + + data.getResultOrThrow() + + //Finalize the login + await loginMessage.finalize(data); + + //Go back to login page + push({ name: 'Login' }) + }) +} + +</script>
\ No newline at end of file diff --git a/front-end/src/views/Login/social/[type].vue b/front-end/src/views/Login/social/[type].vue new file mode 100644 index 0000000..5a803bd --- /dev/null +++ b/front-end/src/views/Login/social/[type].vue @@ -0,0 +1,127 @@ +<template> + <div id="social-login-template" class="app-component-entry"> + <div class="container flex flex-col m-auto my-16"> + <div id="social-final-template" class="flex justify-center"> + <div class="entry-container"> + <h3>Finalizing login</h3> + <div class="mt-6 mb-4"> + <div v-if="message?.length > 0" class="text-lg text-red-500 dark:text-rose-500"> + <p>{{ message }}</p> + <div class="flex justify-center mt-5"> + <router-link to="/login"> + <button type="submit" class="btn primary" :disabled="waiting"> + <fa-icon icon="sign-in-alt" /> + Try again + </button> + </router-link> + </div> + </div> + <div v-else> + <div class="flex justify-center"> + <div class="m-auto"> + <fa-icon class="animate-spin" icon="spinner" size="2x"/> + </div> + </div> + <p>Please wait while we log you in.</p> + </div> + </div> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { isEqual } from 'lodash' +import { useRouteParams, useRouteQuery } from '@vueuse/router' +import { useSession, useWait, useUser, useTitle, configureApiCall } from '@vnuge/vnlib.browser' +import { useRouter } from 'vue-router'; +import { ref } from 'vue' + +useTitle('Social Login') + +const { loggedIn } = useSession() +const { prepareLogin } = useUser() +const { waiting } = useWait() + +const type = useRouteParams('type') +const result = useRouteQuery('result', ''); +const nonce = useRouteQuery('nonce', ''); +const router = useRouter() + +const message = ref('') + +//Override the message handler to capture the error message and display it +const { apiCall } = configureApiCall(m => message.value = m) + +//If logged-in redirect to login page +if (loggedIn.value) { + router.push({ name: 'Login' }) +} + + +const run = async () => { + if (isEqual(result.value, 'authorized')) { + + let loginUrl : string = '' + + switch (type.value) { + case 'github': + loginUrl = '/login/social/github'; + break; + case 'discord': + loginUrl = '/login/social/discord'; + break; + case 'auth0': + loginUrl = '/login/social/auth0'; + break; + default: + router.push('/login') + break; + } + + // If nonce is set, then we can proceed with finalization + await apiCall(async ({ axios }) => { + const preppedLogin = prepareLogin() + // Send the login request + const response = await axios.post(loginUrl, { nonce: nonce.value }) + if (response.data.success === true) { + // Finalize the login + await preppedLogin.finalize(response) + // If the login was successful, then we can redirect to the login page + router.push({ name: 'Login' }) + return + } + // Otherwise, we can show an error + throw { response } + }) + } else { + switch (result.value) { + case 'invalid': + message.value = 'The request was invalid, and you could not be logged in. Please try again.' + break + case 'expired': + message.value = 'The request has expired. Please try again.' + break + default: + message.value = 'There was an error processing the request. Please try again.' + break + } + } +} + +//Run without awaiting +run() + +</script> + +<style lang="scss"> + +#social-login-template{ + .entry-container{ + @apply w-full max-w-[28rem] p-6 text-center sm:border rounded-sm sm:shadow-sm; + @apply sm:bg-white bg-transparent sm:dark:bg-dark-700 dark:border-dark-400; + } +} + +</style> diff --git a/front-end/src/views/Register/components/CompleteReg.vue b/front-end/src/views/Register/components/CompleteReg.vue new file mode 100644 index 0000000..b8f8ef0 --- /dev/null +++ b/front-end/src/views/Register/components/CompleteReg.vue @@ -0,0 +1,116 @@ +<template> + <div id="reg-submit-template"> + <form + id="complete-registration" + method="POST" + action="#" + :disabled="waiting" + @submit.prevent="onSubmit" + > + <fieldset class="input-group"> + <div class="input-container"> + <label for="reg-password" class="pl-1"> + Password + </label> + <input + id="reg-password" + v-model="v$.password.$model" + type="password" + class="input-field primary input" + @input="onInput" + > + </div> + <div class="input-container"> + <label for="reg-repeat-pass" class="pl-1"> + Repeat Password + </label> + <input + id="reg-repeat-pass" + v-model="v$.repeatPass.$model" + type="password" + class="input-field primary input" + @input="onInput" + > + </div> + <div class="flex flex-row justify-end gap-4 pt-4"> + <button form="complete-registration" type="submit" class="btn primary"> + Submit + </button> + <button class="text-red-500 cursor-pointer" @click.prevent="cancel"> + Cancel + </button> + </div> + </fieldset> + </form> + </div> +</template> + +<script setup lang="ts"> + +import { isEqual } from 'lodash' +import useVuelidate from '@vuelidate/core' +import { required, minLength, maxLength, helpers } from '@vuelidate/validators' +import { apiCall, useMessage, useWait, useVuelidateWrapper, WebMessage } from '@vnuge/vnlib.browser' +import { computed, reactive, toRefs } from 'vue' + +const emit = defineEmits(['complete', 'cancel']) + +const props = defineProps<{ + token: string + regPath: string +}>() + +const { token, regPath } = toRefs(props) +const { onInput } = useMessage() +const { waiting } = useWait() + +const vState = reactive({ password: '', repeatPass: ''}) + +const rules = computed(() => { + return { + password: { + required: helpers.withMessage('Password cannot be empty', required), + minLength: helpers.withMessage('Password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage(' must have less than 128 characters', maxLength(128)) + }, + repeatPass: { + sameAs: helpers.withMessage('Your passwords do not match', (value : string) => isEqual(value, v$.value.password.$model)), + required: helpers.withMessage('Repeat password cannot be empty', required), + minLength: helpers.withMessage('Repeat password must be at least 8 characters', minLength(8)), + maxLength: helpers.withMessage('Repeat password must have less than 128 characters', maxLength(128)) + }, + } +}) + +const v$ = useVuelidate(rules, vState, { $lazy: true }) + +const { validate } = useVuelidateWrapper(v$) + +const onSubmit = async function () { + + if (!await validate()) { + return + } + + await apiCall(async ({ axios }) => { + // finalize by passing the token and the new password + const { data } = await axios.post<WebMessage>(regPath.value, { + token: token.value, + password: v$.value.password.$model + }) + + //Throw if not successful + data.getResultOrThrow() + v$.value.$reset() + emit('complete') + + }) +} + +const cancel = () => emit('cancel') + +</script> + +<style> + +</style> diff --git a/front-end/src/views/Register/index.vue b/front-end/src/views/Register/index.vue new file mode 100644 index 0000000..92a5992 --- /dev/null +++ b/front-end/src/views/Register/index.vue @@ -0,0 +1,161 @@ +<template> + <div id="reg-template" class="app-component-entry"> + <div class="container flex flex-col m-auto my-2 duration-150 ease-linear lg:mt-16"> + <div class="text-center"> + <h2>Sign Up</h2> + </div> + <div class="mt-4 content-container"> + <form v-if="formState === 0" @submit.prevent="OnSubmit" :disabled="waiting"> + <fieldset class="input-group"> + <div class="input-container"> + <label for="reg-email" class="pl-1 text-sm">Email Address</label> + <input + id="reg-email" + v-model="v$.emailAddress.$model" + type="email" + placeholder="user@example.com" + required + class="input-field primary" + @input="onInput" + > + </div> + </fieldset> + <fieldset class="flex flex-row justify-between mt-6"> + <div> + <label class="checkbox primary"> + <input v-model="acceptedTerms" type="checkbox"> + <span class="check" /> + <span class="mx-2 text-sm"> + I agree to the + <a class="link" href="#">Terms of Service</a> + </span> + </label> + </div> + <div> + <button type="submit" :disabled="!acceptedTerms || waiting" class="btn primary"> + Submit + </button> + </div> + </fieldset> + </form> + <complete-reg + v-else-if="formState === 1" + :reg-path="regPath" + :token="token" + @cancel="formState = 0" + @complete="formState = 2" + /> + <div v-else> + <div class="text-center"> + <h3>Success</h3> + <fa-icon + :icon="['fa','check-circle']" + class="text-primary-500 dark:text-primary-600" + size="3x" + /> + <p class="mt-4"> + You may log in now. + </p> + </div> + </div> + </div> + </div> + </div> +</template> + +<script setup lang="ts"> +import { isNil } from 'lodash'; +import useVuelidate from '@vuelidate/core' +import { required, maxLength, email, helpers } from '@vuelidate/validators' +import { ref, reactive, watch } from 'vue' +import { useSession, apiCall, useMessage, useWait, useTitle, useVuelidateWrapper } from '@vnuge/vnlib.browser' +import CompleteReg from './components/CompleteReg.vue' +import { useRouter } from 'vue-router'; +import { useRouteQuery } from '@vueuse/router'; + +const regPath = "/account/registration" + +useTitle('Registration') + +const { setMessage, onInput } = useMessage() +const { waiting } = useWait() +const { browserId } = useSession() +const router = useRouter(); + +//Token is the t query argument +const token = useRouteQuery('t', null) + +const acceptedTerms = ref(false) +const formState = ref(isNil(token.value) ? 0 : 1) + +const vState = reactive({ + emailAddress: '' +}) + +const rules = { + emailAddress: { + required: helpers.withMessage('Email cannot be empty', required), + email: helpers.withMessage('Your email address is not valid', email), + maxLength: helpers.withMessage('Email address must be less than 50 characters', maxLength(50)) + } +} +const v$ = useVuelidate(rules, vState, { $lazy: true }) +const { validate } = useVuelidateWrapper(v$) + +const OnSubmit = async function () { + if (!acceptedTerms.value) { + setMessage('You must accept the terms of service to continue.') + return + } + if (!await validate()) { + return + } + await apiCall(async ({ axios, toaster }) => { + const response = await axios.put(regPath, { + username: v$.value.emailAddress.$model, + clientid: browserId.value, + localtime: new Date().toISOString() + }) + if (response.data.success) { + toaster.form.success({ + id: 'logout-success', + title: 'Success', + text: response.data.result, + duration: 5000 + } + ) + + acceptedTerms.value = false + v$.value.emailAddress.$model = ''; + //Clear form + v$.value.$reset() + } else { + setMessage(response.data.result) + } + }) +} + +watch(formState, () => { + //Clear token if formState is not 1 + v$.value.$reset() + acceptedTerms.value = false + v$.value.emailAddress.$model = ''; + router.push({ query: {} }) +}) + +</script> + +<style> +#reg-template .content-container{ + @apply mx-auto p-4 bg-white dark:bg-dark-700 border border-gray-200 dark:border-dark-500; + @apply sm:p-6 sm:rounded-md sm:shadow-sm w-full max-w-sm; +} + +#reg-template input.input-field { + @apply block w-full p-2 border-b-2 my-2; +} + +.content-container fieldset.input-group { + @apply mx-auto; +} +</style> diff --git a/front-end/src/views/[...all].vue b/front-end/src/views/[...all].vue new file mode 100644 index 0000000..1f439fc --- /dev/null +++ b/front-end/src/views/[...all].vue @@ -0,0 +1,24 @@ +<template> + <div id="default-template" class="flex px-4 py-24 app-component-entry"> + <div class="mx-auto"> + <h2 class="text-center">404 - Resource not found</h2> + <p> + The resource you are looking for could not be found on the server. Please check the URL and try again, + or go back <router-link class="link" to="/">home</router-link> + </p> + </div> + </div> +</template> + +<script setup lang="ts"> + +import { useTitle } from '@vnuge/vnlib.browser' +useTitle('404 - Resource not found') + +</script> + +<style lang="scss"> +#default-template{ + +} +</style>
\ No newline at end of file diff --git a/front-end/src/views/index.vue b/front-end/src/views/index.vue new file mode 100644 index 0000000..07b0a09 --- /dev/null +++ b/front-end/src/views/index.vue @@ -0,0 +1,18 @@ +<template> + <div id="home-page-entry" class="app-component-entry"> + + </div> +</template> + +<script setup lang="ts"> + +import { useRouter } from 'vue-router'; + +const { push } = useRouter(); +push('/blog') + +</script> + +<style lang="scss"> + +</style>
\ No newline at end of file diff --git a/front-end/src/vite-env.d.ts b/front-end/src/vite-env.d.ts new file mode 100644 index 0000000..11f02fe --- /dev/null +++ b/front-end/src/vite-env.d.ts @@ -0,0 +1 @@ +/// <reference types="vite/client" /> diff --git a/front-end/tailwind.config.js b/front-end/tailwind.config.js new file mode 100644 index 0000000..8371da2 --- /dev/null +++ b/front-end/tailwind.config.js @@ -0,0 +1,142 @@ +import colors from 'tailwindcss/colors' +import plugin from 'tailwindcss/plugin' + +export default { + cpurge: ['./*.html', './src/**/*.{vue,js,ts,jsx,tsx,css}'], + content: [ + "./index.html", + "./src/**/*.{vue,js,ts,jsx,tsx}", + ], + darkMode: 'class', // or 'media' or 'class' + theme: { + colors: { + //Tw colors + ...colors, + primary: { + default: '#52d9a7', + 50: '#E9FAF4', + 100: '#D8F6EB', + 200: '#B6EFDA', + 300: '#95E8C9', + 400: '#73E0B8', + 500: '#52d9a7', + 600: '#2CC78E', + 700: '#22996D', + 800: '#186B4C', + 900: '#0D3D2B' + }, + secondary: { + default: '#22D2FF', + 50: '#DAF7FF', + 100: '#C5F3FF', + 200: '#9CEBFF', + 300: '#74E3FF', + 400: '#4BDAFF', + 500: '#22D2FF', + 600: '#00B9E9', + 700: '#008DB1', + 800: '#006079', + 900: '#003341' + }, + dark: { + default: '#303843', + 100: '#5a6a7f', + 200: '#4f5d70', + 300: '#455161', + 400: '#3a4452', + 500: '#303843', + 600: '#252c34', + 700: '#1a1f25', + 800: '#101316', + 900: '#050607' + } + }, + }, + plugins: [ + //Adds button variants for each color + plugin(({ theme, addUtilities }) =>{ + const coloredButtons = {} + + for (let color in theme('colors')) { + + //Buttons must also have the .btn class + coloredButtons[`.btn.${color}`] = { + 'color': theme(`colors.white`), + 'border-color': theme(`colors.${color}.500`), + 'background-color': theme(`colors.${color}.500`), + + '&:hover': { + 'border-color': theme(`colors.${color}.600`), + 'background-color': theme(`colors.${color}.600`), + }, + + '&:active': { + 'border-color': theme(`colors.${color}.700`), + 'background-color': theme(`colors.${color}.700`), + }, + + '&:focus': { + '--tw-ring-color': theme(`colors.${color}.500`), + }, + + '&:disabled': { + 'border-color': theme(`colors.${color}.300`), + 'background-color': theme(`colors.${color}.300`), + color: theme(`colors.white`), + }, + + '&.borderless, &.no-border, &.b-0': { + 'color': theme(`colors.${color}.500`), + '&:hover': { + 'color': theme(`colors.${color}.600`), + }, + 'border': 'none', + 'background-color': 'transparent', + }, + + '.dark &': { + 'border-color': theme(`colors.${color}.600`), + 'background-color': 'transparent', + 'color': theme(`colors.${color}.600`), + + '&:hover': { + 'border-color': theme(`colors.${color}.500`), + 'color': theme(`colors.${color}.500`), + }, + + '&:focus': { + '--tw-ring-color': theme(`colors.${color}.500`), + }, + + '&:disabled': { + 'border-color': theme(`colors.${color}.800`), + 'color': theme(`colors.${color}.800`), + } + } + } + } + + addUtilities(coloredButtons) + }), + + //Plugin for input variants + plugin(({ theme, addUtilities }) =>{ + const coloredInputs = {} + + for (let color in theme('colors')) { + coloredInputs[`.input.${color}`] = { + 'outline': 'none', + + '&:focus, &:active, &::after, &:focus:hover':{ + 'border-color': theme(`colors.${color}.500`), + 'dark &':{ + 'border-color': theme(`colors.${color}.600`), + } + } + } + } + + addUtilities(coloredInputs) + }) + ] +} diff --git a/front-end/tsconfig.json b/front-end/tsconfig.json new file mode 100644 index 0000000..f82888f --- /dev/null +++ b/front-end/tsconfig.json @@ -0,0 +1,25 @@ +{ + "compilerOptions": { + "target": "ES2020", + "useDefineForClassFields": true, + "module": "ESNext", + "lib": ["ES2020", "DOM", "DOM.Iterable"], + "skipLibCheck": true, + + /* Bundler mode */ + "moduleResolution": "bundler", + "allowImportingTsExtensions": true, + "resolveJsonModule": true, + "isolatedModules": true, + "noEmit": true, + "jsx": "preserve", + + /* Linting */ + "strict": true, + "noUnusedLocals": true, + "noUnusedParameters": true, + "noFallthroughCasesInSwitch": true + }, + "include": ["src/**/*.ts", "src/**/*.d.ts", "src/**/*.tsx", "src/**/*.vue"], + "references": [{ "path": "./tsconfig.node.json" }] +} diff --git a/front-end/tsconfig.node.json b/front-end/tsconfig.node.json new file mode 100644 index 0000000..42872c5 --- /dev/null +++ b/front-end/tsconfig.node.json @@ -0,0 +1,10 @@ +{ + "compilerOptions": { + "composite": true, + "skipLibCheck": true, + "module": "ESNext", + "moduleResolution": "bundler", + "allowSyntheticDefaultImports": true + }, + "include": ["vite.config.ts"] +} diff --git a/front-end/vite.config.ts b/front-end/vite.config.ts new file mode 100644 index 0000000..a6e7ed0 --- /dev/null +++ b/front-end/vite.config.ts @@ -0,0 +1,59 @@ +// Copyright (C) 2023 Vaughn Nugent +// +// This program is free software: you can redistribute it and/or modify +// it under the terms of the GNU Affero General Public License as +// published by the Free Software Foundation, either version 3 of the +// License, or (at your option) any later version. +// +// This program is distributed in the hope that it will be useful, +// but WITHOUT ANY WARRANTY; without even the implied warranty of +// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +// GNU Affero General Public License for more details. +// +// You should have received a copy of the GNU Affero General Public License +// along with this program. If not, see <https://www.gnu.org/licenses/>. + +import { defineConfig } from 'vite' +import vue from '@vitejs/plugin-vue' +import postcss from './postcss.config.js' +import { resolve } from 'path' +import { server } from './vite.config.local.ts' + +//Pages setup +import Pages from 'vite-plugin-pages' + +// https://vitejs.dev/config/ +export default defineConfig({ + build: { + cssCodeSplit: true, + rollupOptions: { + plugins: [], + output: { + manualChunks: { + vnlib: ['@vnuge/vnlib.browser'], + cmnex: ['@vnuge/cmnext-admin'], + } + } + }, + }, + css: { + postcss: postcss + }, + plugins: [ + vue(), + //Setup the vite pages plugin + Pages({ + extensions: ['vue'], + dirs: 'src/views', + exclude: ['**/components/**'], + }), + ], + resolve: { + alias: { + "@": resolve(__dirname, "./src"), + } + }, + server: { + ...server + } +}) |