diff --git a/.github/workflows/e2e-tests.yml b/.github/workflows/e2e-tests.yml index 13b787ef46..9729845bec 100644 --- a/.github/workflows/e2e-tests.yml +++ b/.github/workflows/e2e-tests.yml @@ -7,7 +7,7 @@ on: jobs: test: timeout-minutes: 10 - runs-on: ubuntu-latest + runs-on: ubuntu-22.04 strategy: matrix: shard: [1/3, 2/3, 3/3] @@ -23,7 +23,7 @@ jobs: - name: Get Playwright version id: playwright-version - run: echo "PLAYWRIGHT_VERSION=$(jq -r .dependencies.playwright package.json)" >> "$GITHUB_OUTPUT" + run: echo "PLAYWRIGHT_VERSION=$(jq -r '.devDependencies[\"@playwright/test\"] // .dependencies.playwright // \"unknown\"' package.json)" >> "$GITHUB_OUTPUT" - name: Install dependencies run: pnpm install diff --git a/locales/en.yml b/locales/en.yml index d03d80d3f6..b6932a8298 100644 --- a/locales/en.yml +++ b/locales/en.yml @@ -93,6 +93,32 @@ tools: button: copy: Copy refresh: Refresh + + icon-generator: + title: Icon generator + description: Generate resized app icons from one source image for PWA, Android, and iOS presets. + presets: Presets + presetLabels: + pwa: PWA + android: Android + ios: iOS + fitMode: Fit mode + cover: Cover (crop) + contain: Contain + clearPresetSizes: Clear preset sizes + addSize: Add size + baseName: Base name + baseNameHint: File names use "-x.png". Invalid characters are replaced with "-". + includeManifest: Include manifest.json in ZIP + selectedSizes: Selected output sizes + uploadTitle: Drag and drop an image here, or click to select an image + noImage: Upload an image to start. + generate: Generate icons + output: Generated icons + download: Download + downloadAll: Download all + downloadZip: Download zip + percentage-calculator: title: Percentage calculator description: Easily calculate percentages from a value to another value, or from a percentage to a value. diff --git a/package.json b/package.json index 5738b6325a..3ceae53759 100644 --- a/package.json +++ b/package.json @@ -72,6 +72,7 @@ "ibantools": "^4.3.3", "js-base64": "^3.7.6", "json5": "^2.2.3", + "jszip": "^3.10.1", "jwt-decode": "^3.1.2", "libphonenumber-js": "^1.10.28", "lodash": "^4.17.21", @@ -149,5 +150,10 @@ "vitest": "^0.34.0", "workbox-window": "^7.0.0", "zx": "^7.2.1" + }, + "pnpm": { + "overrides": { + "@vueuse/shared": "10.3.0" + } } } diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 5d11389323..dd0f24f92a 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -4,6 +4,9 @@ settings: autoInstallPeers: true excludeLinksFromLockfile: false +overrides: + '@vueuse/shared': 10.3.0 + importers: .: @@ -49,7 +52,7 @@ importers: version: 10.3.0(vue@3.3.4) '@vueuse/head': specifier: ^1.0.0 - version: 1.0.0(typescript@5.2.2)(vue@3.3.4) + version: 1.0.0(vue@3.3.4) '@vueuse/router': specifier: ^10.0.0 version: 10.0.0(vue-router@4.1.6(vue@3.3.4))(vue@3.3.4) @@ -113,6 +116,9 @@ importers: json5: specifier: ^2.2.3 version: 2.2.3 + jszip: + specifier: ^3.10.1 + version: 3.10.1 jwt-decode: specifier: ^3.1.2 version: 3.1.2 @@ -356,17 +362,20 @@ packages: '@antfu/eslint-config-basic@0.41.0': resolution: {integrity: sha512-zcwFv+nEV/NroeeVWriNdnIGd9soOLRG8wIiVz4VVJ0BjONrqQR56HLG/gDxH/1GUYBnQCEcVxGUmegce08cnw==} + deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config peerDependencies: eslint: '>=7.4.0' '@antfu/eslint-config-ts@0.41.0': resolution: {integrity: sha512-ng3GYpJGZgrxGwBVda/MgUpveH3LbEqdPCFi1+S5e62W4kf8rmEVbhc0I8w7/aKN0uNqir5SInYg8gob2maDAQ==} + deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config peerDependencies: eslint: '>=7.4.0' typescript: '>=3.9' '@antfu/eslint-config-vue@0.41.0': resolution: {integrity: sha512-iJiEGRUgRmT3mQCmGl0hTMwq/ShXRjRPjpgsDcphKJyEF06ZIR/4gxHn+utQRLT2hD39DQN8gk0ZbpV3gWtf/g==} + deprecated: Deprecated, please migrate to @antfu/eslint-config with the flat config peerDependencies: eslint: '>=7.4.0' @@ -1464,6 +1473,7 @@ packages: '@humanwhocodes/config-array@0.11.10': resolution: {integrity: sha512-KVVjQmNUepDVGXNuoRRdmmEjruj0KfiGSbS8LVc12LMsWDQzRXJ0qdhN8L8uUigKpfEHRhlaQFY0ib1tnUbNeQ==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -1471,6 +1481,7 @@ packages: '@humanwhocodes/object-schema@1.2.1': resolution: {integrity: sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA==} + deprecated: Use @eslint/object-schema instead '@iconify-json/mdi@1.1.50': resolution: {integrity: sha512-SgbT5w5eHCdOG74ZWPz7HlTGk6VsifIJhNi6lAsxj/5Nlqt6Cz4LlQmSa9eecU9p075Jub2aAx/o7YI+GCahRQ==} @@ -1629,6 +1640,7 @@ packages: '@playwright/test@1.32.3': resolution: {integrity: sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==} engines: {node: '>=14'} + deprecated: Please update to the latest version of Playwright to test up-to-date browsers. hasBin: true '@polka/url@1.0.0-next.28': @@ -2303,9 +2315,6 @@ packages: '@vue/compiler-core@3.3.7': resolution: {integrity: sha512-pACdY6YnTNVLXsB86YD8OF9ihwpolzhhtdLVHhBL6do/ykr6kKXNYABRtNMGrsQXpEXXyAdwvWWkuTbs4MFtPQ==} - '@vue/compiler-core@3.5.13': - resolution: {integrity: sha512-oOdAkwqUfW1WqpwSYJce06wvt6HljgY3fGeM9NcVA1HaYOij3mZG9Rkysn0OHuyUAGMbEbARIpsG+LPVlBJ5/Q==} - '@vue/compiler-dom@3.2.47': resolution: {integrity: sha512-dBBnEHEPoftUiS03a4ggEig74J2YBZ2UIeyfpcRM2tavgMWo4bsEfgCGsu+uJIL/vax9S+JztH8NmQerUo7shQ==} @@ -2315,18 +2324,12 @@ packages: '@vue/compiler-dom@3.3.7': resolution: {integrity: sha512-0LwkyJjnUPssXv/d1vNJ0PKfBlDoQs7n81CbO6Q0zdL7H1EzqYRrTVXDqdBVqro0aJjo/FOa1qBAPVI4PGSHBw==} - '@vue/compiler-dom@3.5.13': - resolution: {integrity: sha512-ZOJ46sMOKUjO3e94wPdCzQ6P1Lx/vhp2RSvfaab88Ajexs0AHeV0uasYhi99WPaogmBlRHNRuly8xV75cNTMDA==} - '@vue/compiler-sfc@3.2.47': resolution: {integrity: sha512-rog05W+2IFfxjMcFw10tM9+f7i/+FFpZJJ5XHX72NP9eC2uRD+42M3pYcQqDXVYoj74kHMSEdQ/WmCjt8JFksQ==} '@vue/compiler-sfc@3.3.4': resolution: {integrity: sha512-6y/d8uw+5TkCuzBkgLS0v3lSM3hJDntFEiUORM11pQ/hKvkhSKZrXW6i69UyXlJQisJxuUEJKAWEqWbWsLeNKQ==} - '@vue/compiler-sfc@3.5.13': - resolution: {integrity: sha512-6VdaljMpD82w6c2749Zhf5T9u5uLBWKnVue6XWxprDobftnletJ8+oel7sexFfM3qIxNmVE7LSFGTpv6obNyaQ==} - '@vue/compiler-ssr@3.2.47': resolution: {integrity: sha512-wVXC+gszhulcMD8wpxMsqSOpvDZ6xKXSVWkf50Guf/S+28hTAXPDYRTbLQ3EDkOP5Xz/+SY37YiwDquKbJOgZw==} @@ -2336,9 +2339,6 @@ packages: '@vue/compiler-ssr@3.3.7': resolution: {integrity: sha512-TxOfNVVeH3zgBc82kcUv+emNHo+vKnlRrkv8YvQU5+Y5LJGJwSNzcmLUoxD/dNzv0bhQ/F0s+InlgV0NrApJZg==} - '@vue/compiler-ssr@3.5.13': - resolution: {integrity: sha512-wMH6vrYHxQl/IybKJagqbquvxpWCuVYpoUJfCqFZwa/JY1GdATAQ+TgVtgrwwMZ0D07QhA99rs/EAAWfvG6KpA==} - '@vue/devtools-api@6.5.0': resolution: {integrity: sha512-o9KfBeaBmCKl10usN4crU53fYtC1r7jJwdGKjPT24t348rHxgfpZ0xL3Xm/gLUYnc0oTp8LAmrxOeLyu6tbk2Q==} @@ -2359,21 +2359,12 @@ packages: '@vue/reactivity@3.3.4': resolution: {integrity: sha512-kLTDLwd0B1jG08NBF3R5rqULtv/f8x3rOFByTDz4J53ttIQEDmALqKqXY0J+XQeN0aV2FBxY8nJDf88yvOPAqQ==} - '@vue/reactivity@3.5.13': - resolution: {integrity: sha512-NaCwtw8o48B9I6L1zl2p41OHo/2Z4wqYGGIK1Khu5T7yxrn+ATOixn/Udn2m+6kZKB/J7cuT9DbWWhRxqixACg==} - '@vue/runtime-core@3.3.4': resolution: {integrity: sha512-R+bqxMN6pWO7zGI4OMlmvePOdP2c93GsHFM/siJI7O2nxFRzj55pLwkpCedEY+bTMgp5miZ8CxfIZo3S+gFqvA==} - '@vue/runtime-core@3.5.13': - resolution: {integrity: sha512-Fj4YRQ3Az0WTZw1sFe+QDb0aXCerigEpw418pw1HBUKFtnQHWzwojaukAs2X/c9DQz4MQ4bsXTGlcpGxU/RCIw==} - '@vue/runtime-dom@3.3.4': resolution: {integrity: sha512-Aj5bTJ3u5sFsUckRghsNjVTtxZQ1OyMWCr5dZRAPijF/0Vy4xEoRCwLyHXcj4D0UFbJ4lbx3gPTgg06K/GnPnQ==} - '@vue/runtime-dom@3.5.13': - resolution: {integrity: sha512-dLaj94s93NYLqjLiyFzVs9X6dWhTdAlEAciC3Moq7gzAc13VJUdCnjjRurNM6uTLFATRHexHCTu/Xp3eW6yoog==} - '@vue/server-renderer@3.3.4': resolution: {integrity: sha512-Q6jDDzR23ViIb67v+vM1Dqntu+HUexQcsWKhhQa4ARVzxOY2HbC7QRW/ggkDBd5BU+uM1sV6XOAP0b216o34JQ==} peerDependencies: @@ -2384,11 +2375,6 @@ packages: peerDependencies: vue: 3.3.7 - '@vue/server-renderer@3.5.13': - resolution: {integrity: sha512-wAi4IRJV/2SAW3htkTlB+dHeRmpTiVIK1OGLWV1yeStVSebSQQOwGwIq0D3ZIoBj2C2qpgz5+vX9iEBkTdk5YA==} - peerDependencies: - vue: 3.5.13 - '@vue/shared@3.2.47': resolution: {integrity: sha512-BHGyyGN3Q97EZx0taMQ+OLNuZcW3d37ZEVmEAyeoA9ERdGvm9Irc/0Fua8SNyOtV1w6BS4q25wbMzJujO9HIfQ==} @@ -2398,9 +2384,6 @@ packages: '@vue/shared@3.3.7': resolution: {integrity: sha512-N/tbkINRUDExgcPTBvxNkvHGu504k8lzlNQRITVnm6YjOjwa4r0nnbd4Jb01sNpur5hAllyRJzSK5PvB9PPwRg==} - '@vue/shared@3.5.13': - resolution: {integrity: sha512-/hnE/qP5ZoGpol0a5mDi45bOd7t3tjYJBjsgCsivow7D48cJeV5l05RD82lPqi7gRiphZM37rnhW1l6ZoCNNnQ==} - '@vue/test-utils@2.3.2': resolution: {integrity: sha512-hJnVaYhbrIm0yBS0+e1Y0Sj85cMyAi+PAbK4JHqMRUZ6S622Goa+G7QzkRSyvCteG8wop7tipuEbHoZo26wsSA==} peerDependencies: @@ -2428,20 +2411,15 @@ packages: peerDependencies: vue-router: '>=4.0.0-rc.1' - '@vueuse/shared@10.0.0': - resolution: {integrity: sha512-Zh3LgJqvUBWVY3SiMvXanTcfAneGbt63QPczBRDNgQ6jd/ehodO9a1lCFzaA6SWJJoI+ugVTjHFYJdoR656DVQ==} - '@vueuse/shared@10.3.0': resolution: {integrity: sha512-kGqCTEuFPMK4+fNWy6dUOiYmxGcUbtznMwBZLC1PubidF4VZY05B+Oht7Jh7/6x4VOWGpvu3R37WHi81cKpiqg==} - '@vueuse/shared@12.0.0': - resolution: {integrity: sha512-3i6qtcq2PIio5i/vVYidkkcgvmTjCqrf26u+Fd4LhnbBmIT6FN8y6q/GJERp8lfcB9zVEfjdV0Br0443qZuJpw==} - '@zhead/schema@1.0.0-beta.13': resolution: {integrity: sha512-P1A1vRGFBhITco8Iw4/hvnDYoE/SoVrd71dW1pBFdXJb3vP+pBtoOuhbEKy0ROJGOyzQuqvFibcwzyLlWMqNiQ==} abab@2.0.6: resolution: {integrity: sha512-j2afSsaIENvHZN2B8GOpF566vZ5WVk5opAiMTvWgaQT8DkbOqsTfvNAvHoRGU2zzP8cPoqys+xHTRDWW8L+/BA==} + deprecated: Use your platform's native atob() and btoa() methods instead abbrev@1.1.1: resolution: {integrity: sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==} @@ -2552,6 +2530,7 @@ packages: babel-merge@3.0.0: resolution: {integrity: sha512-eBOBtHnzt9xvnjpYNI5HmaPp/b2vMveE5XggzqHnQeHJ8mFIBrBv6WZEVIj5jJ2uwTItkqKo9gWzEEcBxEq0yw==} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. peerDependencies: '@babel/core': ^7.0.0 @@ -2798,7 +2777,7 @@ packages: hasBin: true concat-map@0.0.1: - resolution: {integrity: sha1-2Klr13/Wjfd5OnMDajug1UBdR3s=} + resolution: {integrity: sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==} confbox@0.1.8: resolution: {integrity: sha512-RMtmw0iFkeR4YV+fUOSucriAQNb9g8zFR52MWCtl+cCZOFRNL6zeB395vPzFhEjjn4fMxXudmELnl/KF/WrK6w==} @@ -2831,6 +2810,9 @@ packages: core-js-compat@3.39.0: resolution: {integrity: sha512-VgEUx3VwlExr5no0tXlBt+silBvhTryPwCXRI2Id1PN8WTKu7MreethvddqOubrYxkFdv/RnYrqlv1sFNAUelw==} + core-util-is@1.0.3: + resolution: {integrity: sha512-ZQBvi1DcpJ4GDqanjucZ2Hj3wEO5pZDS89BWbkcrvdxksJorwUDDZamX9ldFkp9aw2lmBDLgkObEA4DWNJ9FYQ==} + country-code-lookup@0.1.0: resolution: {integrity: sha512-IOI66HEG+8bXfWPy+sTzuN7161vmDZOHg1wgIPFf3WfD73FeLajnn6C+fnxOIa9RL1WRBDMXQQWW/FOaOYaQ3w==} @@ -2896,9 +2878,6 @@ packages: csstype@3.1.2: resolution: {integrity: sha512-I7K1Uu0MBPzaFKg4nI5Q7Vs2t+3gWWW648spaF+Rg7pI9ds18Ugn+lvg4SHczUdKlHI5LWBXyqfS8+DufyBsgQ==} - csstype@3.1.3: - resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - dash-get@1.0.2: resolution: {integrity: sha512-4FbVrHDwfOASx7uQVxeiCTo7ggSdYZbqs8lH+WU6ViypPlDbe9y6IP5VVUDQBv9DcnyaiPT5XT0UWHgJ64zLeQ==} @@ -3054,6 +3033,7 @@ packages: domexception@4.0.0: resolution: {integrity: sha512-A2is4PLG+eeSfoTMA95/s4pvAoSo2mKtiM5jlHkAVewmiO8ISFTFKZjH7UAM1Atli/OT/7JHOrJRJiMKUZKYBw==} engines: {node: '>=12'} + deprecated: Use your platform's native DOMException instead domhandler@5.0.3: resolution: {integrity: sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==} @@ -3246,6 +3226,7 @@ packages: eslint-plugin-i@2.28.0-2: resolution: {integrity: sha512-z48kG4qmE4TmiLcxbmvxMT5ycwvPkXaWW0XpU1L768uZaTbiDbxsHMEdV24JHlOR1xDsPpKW39BfP/pRdYIwFA==} engines: {node: '>=12'} + deprecated: Please migrate to the brand new `eslint-plugin-import-x` instead peerDependencies: eslint: ^7.2.0 || ^8 @@ -3271,6 +3252,7 @@ packages: eslint-plugin-markdown@3.0.1: resolution: {integrity: sha512-8rqoc148DWdGdmYF6WSQFT3uQ6PO7zXYgeBpHAOAakX/zpq+NvFYbDA/H7PYzHajwtmaOzAwfxyl++x0g1/N9A==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: Please use @eslint/markdown instead peerDependencies: eslint: ^6.0.0 || ^7.0.0 || ^8.0.0 @@ -3337,6 +3319,7 @@ packages: eslint@8.47.0: resolution: {integrity: sha512-spUQWrdPt+pRVP1TTJLmfRNJJHHZryFmptzcafwSvHsceV81djHOdnEeDmkdotZyLNjDhrOasNK8nikkoG1O8Q==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true espree@9.6.1: @@ -3566,10 +3549,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Old versions of glob are not supported, and contain widely publicized security vulnerabilities, which have been fixed in the current version. Please update. Support for old versions may be purchased (at exorbitant rates) by contacting i@izs.me globals@11.12.0: resolution: {integrity: sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==} @@ -3737,6 +3722,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + immediate@3.0.6: + resolution: {integrity: sha512-XXOFtyqDjNDAQxVfYxuF7g9Il/IbWmmlQg2MYKOH8ExIT1qg6xc4zyS3HaEEATgs1btfzxq15ciUiY7gjSXRGQ==} + import-fresh@3.3.0: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} @@ -3758,6 +3746,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -3974,6 +3963,9 @@ packages: resolution: {integrity: sha512-fKzAra0rGJUUBwGBgNkHZuToZcn+TtXHpeCgmkMJMMYx1sQDYaCSyjJBSCa2nH1DGm7s3n1oBnohoVTBaN7Lww==} engines: {node: '>=8'} + isarray@1.0.0: + resolution: {integrity: sha512-VLghIWNM6ELQzo7zwmcg0NmTVyWKYjvIeM83yjp0wRDTmUnrM678fQbcKBo6n2CJEF0szoG//ytg+TKla89ALQ==} + isarray@2.0.5: resolution: {integrity: sha512-xHjhDr3cNBK0BzdUJSPXZntQUx/mwMS5Rw4A7lPJ90XGAO6ISP/ePDNuo0vhqOZU+UD5JoodwCAAoZQd3FeAKw==} @@ -4096,6 +4088,9 @@ packages: resolution: {integrity: sha512-p/nXbhSEcu3pZRdkW1OfJhpsVtW1gd4Wa1fnQc9YLiTfAjn0312eMKimbdIQzuZl9aa9xUGaRlP9T/CJE/ditQ==} engines: {node: '>=0.10.0'} + jszip@3.10.1: + resolution: {integrity: sha512-xXDvecyTpGLrqFrvkrUSoxxfJI5AH7U8zxxtVclpsUtMCq4JQ290LY8AW5c7Ggnr/Y/oK+bQMbqK2qmtk3pN4g==} + jwt-decode@3.1.2: resolution: {integrity: sha512-UfpWE/VZn0iP50d8cz9NrZLM9lSWhcJ+0Gt/nm4by88UL+J1SiKN8/5dkjMmbEzwL2CAe+67GsegCbIKtbp75A==} @@ -4122,6 +4117,9 @@ packages: libphonenumber-js@1.10.28: resolution: {integrity: sha512-1eAgjLrZA0+2Wgw4hs+4Q/kEBycxQo8ZLYnmOvZ3AlM8ImAVAJgDPlZtISLEzD1vunc2q8s2Pn7XwB7I8U3Kzw==} + lie@3.3.0: + resolution: {integrity: sha512-UaiMJzeWRlEujzAuw5LokY1L5ecNQYZKfmyZ9L7wDHb/p5etKaxXhohBcrw0EYby+G/NA52vRSN4N39dxHAIwQ==} + lines-and-columns@1.2.4: resolution: {integrity: sha512-7ylylesZQ/PV29jhEDl3Ufjo6ZX7gCqJr5F7PKrqc93v7fzSymt1BpwEU8nAUXs8qzzvqhbjhK5QZg6Mt/HkBg==} @@ -4172,6 +4170,7 @@ packages: loupe@2.3.6: resolution: {integrity: sha512-RaPMZKiMy8/JruncMU5Bt6na1eftNoo++R4Y+N2FrxkDVTrGvcyzFTsaGif4QTeKESheMGegbhw6iUAq+5A8zA==} + deprecated: Please upgrade to 2.3.7 which fixes GHSA-4q6p-r6v2-jvc5 lower-case-first@1.0.2: resolution: {integrity: sha512-UuxaYakO7XeONbKrZf5FEgkantPf5DUqDayzP5VXZrtRPdH86s4kN47I8B3TW10S4QKiE3ziHNf3kRN//okHjA==} @@ -4380,6 +4379,7 @@ packages: node-domexception@1.0.0: resolution: {integrity: sha512-/jKZoMpw0F8GRwl4/eLROPA3cfcXtLApP0QzLmUT/HuPCZWyB7IY9ZrMeKw2O/nFIqPQB3PVM9aYm0F312AXDQ==} engines: {node: '>=10.5.0'} + deprecated: Use your platform's native DOMException instead node-fetch-native@1.6.4: resolution: {integrity: sha512-IhOigYzAKHd244OC0JIMIUrjzctirCmPkaIfhDeGcEETWof5zKYUW7e7MYvChGWh/4CJeXEgsRyGzuF334rOOQ==} @@ -4498,6 +4498,9 @@ packages: package-manager-detector@0.2.7: resolution: {integrity: sha512-g4+387DXDKlZzHkP+9FLt8yKj8+/3tOkPv7DVTJGGRm00RkEWgqbFstX1mXJ4M0VDYhUqsTOiISqNOJnhAu3PQ==} + pako@1.0.11: + resolution: {integrity: sha512-4hLB8Py4zZce5s4yd9XzopqwVv/yGNhV1Bl8NTmCq1763HeK2+EwVTv+leGeL13Dnh2wfbqowVPXCIO0z4taYw==} + param-case@2.1.1: resolution: {integrity: sha512-eQE845L6ot89sk2N8liD8HAuH4ca6Vvr7VWAWwt7+kvvG5aBcPmmphQ68JsEG2qa9n1TykS2DLeMt363AAH8/w==} @@ -4610,6 +4613,7 @@ packages: plausible-tracker@0.3.8: resolution: {integrity: sha512-lmOWYQ7s9KOUJ1R+YTOR3HrjdbxIS2Z4de0P/Jx2dQPteznJl2eX3tXxKClpvbfyGP59B5bbhW8ftN59HbbFSg==} engines: {node: '>=10'} + deprecated: Package no longer supported. Contact Support at https://www.npmjs.com/support for more info. playwright-core@1.32.3: resolution: {integrity: sha512-SB+cdrnu74ZIn5Ogh/8278ngEh9NEEV0vR4sJFmK04h2iZpybfbqBY0bX6+BLYWVdV12JLLI+JEFtSnYgR+mWg==} @@ -4665,6 +4669,9 @@ packages: resolution: {integrity: sha512-1q0oC8eRveTg5nnBEWMXAU2qpv65Gnuf2eCQzSjxpWFkPaPARwqZZDGuNE0zPAZfTCHzIk3A8dIjwlQKKLphyg==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} + process-nextick-args@2.0.1: + resolution: {integrity: sha512-3ouUOpQhtgrbOa17J7+uxOTpITYWaGP7/AhoR3+A+/1e9skrzelGi/dXzEYyvbxubEF6Wn2ypscTKiKJFFn1ag==} + prosemirror-changeset@2.2.1: resolution: {integrity: sha512-J7msc6wbxB4ekDFj+n9gTW/jav/p53kdlivvuppHsrZXCaQdVgRghoZbSS3kwrRyAstRVQ4/+u5k7YfLgkkQvQ==} @@ -4788,6 +4795,9 @@ packages: resolution: {integrity: sha512-Ug69mNOpfvKDAc2Q8DRpMjjzdtrnv9HcSMX+4VsZxD1aZ6ZzrIE7rlzXBtWTyhULSMKg076AW6WR5iZpD0JiOg==} engines: {node: '>=8'} + readable-stream@2.3.8: + resolution: {integrity: sha512-8p0AUk4XODgIewSi0l8Epjs+EVnWiK7NoDIEGU0HhE7+ZyY8D1IMY7odu5lRrFXGg71L15KG8QrPmum45RTtdA==} + readable-stream@3.6.2: resolution: {integrity: sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==} engines: {node: '>= 6'} @@ -4868,10 +4878,6 @@ packages: resolution: {integrity: sha512-PXNdCiPqDqeUou+w1C2eTQbNfxKSuMxqTCuvlmmMsk1NWHL5fRrhY6Pl0qEYYc6+QqGClco1Qj8XnjPego4wfg==} hasBin: true - resolve@1.22.8: - resolution: {integrity: sha512-oKWePCxqpd6FlLvGV1VU0x7bkPmmCNolxzjMf4NczoDnQcIWrAF+cPtZn5i6n+RfD2d9i0tzpKnG6Yk168yIyw==} - hasBin: true - resolve@1.22.9: resolution: {integrity: sha512-QxrmX1DzraFIi9PxdG5VkRfRwIgjwyud+z/iBwfRRrVmHc+P9Q7u2lSSpQ6bjr2gy5lrqIiU9vb6iAeGf2400A==} hasBin: true @@ -4894,6 +4900,7 @@ packages: rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true rollup@2.79.2: @@ -4919,6 +4926,9 @@ packages: resolution: {integrity: sha512-AURm5f0jYEOydBj7VQlVvDrjeFgthDdEF5H1dP+6mNpoXOMo1quQqJ4wvJDyRZ9+pO3kGWoOdmV08cSv2aJV6Q==} engines: {node: '>=0.4'} + safe-buffer@5.1.2: + resolution: {integrity: sha512-Gd2UZBJDkXlY7GbJxfsE8/nvKkUEU1G38c1siN6QP6a9PT9MmHB8GnpscSmMJSoF8LOIrt8ud/wPtojys4G6+g==} + safe-buffer@5.2.1: resolution: {integrity: sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==} @@ -4996,6 +5006,9 @@ packages: resolution: {integrity: sha512-zTEg4HL0RwVrqcWs3ztF+x1vkxfm0lP+MQQFPiMJTKVceBwEV0A569Ou8l9IYQG8jOZdMVI1hGsc0tmeD2o/Lw==} engines: {node: '>=11.0'} + setimmediate@1.0.5: + resolution: {integrity: sha512-MATJdZp8sLqDl/68LfQmbP8zKPLQNV6BIZoIgrscFDQ+RsvK/BxeDQOgyxKKoh0y/8h3BqVFnCqQ/gd+reiIXA==} + shebang-command@2.0.0: resolution: {integrity: sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==} engines: {node: '>=8'} @@ -5071,9 +5084,11 @@ packages: source-map@0.8.0-beta.0: resolution: {integrity: sha512-2ymg6oRBpebeZi9UUNsgQ89bhx01TcTkmNTGnNO88imTmbSgy4nfujrgVEFKWpMTEGA11EDkTt7mqObTPdigIA==} engines: {node: '>= 8'} + deprecated: The work that was done in this beta branch won't be included in future versions sourcemap-codec@1.4.8: resolution: {integrity: sha512-9NykojV5Uih4lgo5So5dtw+f0JgJX30KCNI8gwhz2J9A15wD0Ml6tjHKwf6fTSa6fAdVBdZeNOs9eJ71qCk8vA==} + deprecated: Please use @jridgewell/sourcemap-codec instead spdx-correct@3.2.0: resolution: {integrity: sha512-kN9dJbvnySHULIluDHy32WHRUu3Og7B9sbY7tsFLctQkIqnMh3hErYgdMjTYuqmcXX+lK5T1lnUt3G7zNswmZA==} @@ -5132,6 +5147,9 @@ packages: resolution: {integrity: sha512-UXSH262CSZY1tfu3G3Secr6uGLCFVPMhIqHjlgCUtCCcgihYc/xKs9djMTMUOb2j1mVSeU8EU6NWc/iQKU6Gfg==} engines: {node: '>= 0.4'} + string_decoder@1.1.1: + resolution: {integrity: sha512-n/ShnvDi6FHbbVfviro+WojiFzv+s8MPMHBczVePfUpDJLwoLT0ht1l4YwBCbi8pJAveEEdnkHyPyTP/mzRfwg==} + string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} @@ -5569,6 +5587,7 @@ packages: vite-plugin-vue-markdown@0.23.5: resolution: {integrity: sha512-NXTZ4y+n691gLPWayMBbh4jldQeaqDp9e9WjWUYbn9obsLqS9qU+hr4RAruDq5kP4siTOp7JDV34Sw5eA7WxLg==} + deprecated: '`vite-plugin-vue-markdown` is renamed to `unplugin-vue-markdown`. For usages in Vite, you also need to change the import path to `unplugin-vue-markdown/vite`.' peerDependencies: vite: ^2.0.0 || ^3.0.0-0 || ^4.0.0 @@ -5675,6 +5694,7 @@ packages: vue-i18n@9.9.1: resolution: {integrity: sha512-xyQ4VspLdNSPTKBFBPWa1tvtj+9HuockZwgFeD2OhxxXuC2CWeNvV4seu2o9+vbQOyQbhAM5Ez56oxUrrnTWdw==} engines: {node: '>= 16'} + deprecated: v9 and v10 no longer supported. please migrate to v11. about maintenance status, see https://vue-i18n.intlify.dev/guide/maintenance.html peerDependencies: vue: ^3.0.0 @@ -5701,14 +5721,6 @@ packages: vue@3.3.4: resolution: {integrity: sha512-VTyEYn3yvIeY1Py0WaYGZsXnz3y5UnGi62GjVEqvEGPl6nxbOrCXbVOTQWBEJUqAyTUk2uJ5JLVnYJ6ZzGbrSw==} - vue@3.5.13: - resolution: {integrity: sha512-wmeiSMxkZCSc+PM2w2VRsOYAZC8GdipNFRTsLSfodVqI9mbejKeXEGr8SckuLnrQPGe3oJN5c3K0vpoU9q/wCQ==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - vuedraggable@4.1.0: resolution: {integrity: sha512-FU5HCWBmsf20GpP3eudURW3WdWTKIbEIQxh9/8GE806hydR9qZqRRxRE3RjqX7PkuLuMQG/A7n3cfj9rCEchww==} peerDependencies: @@ -5754,6 +5766,7 @@ packages: whatwg-encoding@2.0.0: resolution: {integrity: sha512-p41ogyeMUrw3jWclHWTQg1k05DSVXPLcVxRTYsXUk+ZooOCZLcoYgPZ/HL/D/N+uQPOtcp1me1WhBEaX02mhWg==} engines: {node: '>=12'} + deprecated: Use @exodus/bytes instead for a more spec-conformant and faster implementation whatwg-mimetype@3.0.0: resolution: {integrity: sha512-nt+N2dzIutVRxARx1nghPKGv1xHikU7HKdfafKkLNLindmPU/ch3U31NOCGGA/dmPcmb1VlofO0vnKAcsm0o/Q==} @@ -6117,7 +6130,7 @@ snapshots: '@babel/traverse': 7.23.2 '@babel/types': 7.23.0 convert-source-map: 2.0.0 - debug: 4.3.4 + debug: 4.4.0 gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -7030,7 +7043,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.22.6 '@babel/parser': 7.23.0 '@babel/types': 7.23.0 - debug: 4.3.4 + debug: 4.4.0 globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -7401,7 +7414,7 @@ snapshots: '@linaria/logger@4.0.0': dependencies: - debug: 4.3.4 + debug: 4.4.0 picocolors: 1.0.0 transitivePeerDependencies: - supports-color @@ -7922,7 +7935,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2) '@typescript-eslint/utils': 6.4.1(eslint@8.47.0)(typescript@5.2.2) - debug: 4.3.4 + debug: 4.4.0 eslint: 8.47.0 ts-api-utils: 1.0.1(typescript@5.2.2) optionalDependencies: @@ -7940,10 +7953,10 @@ snapshots: dependencies: '@typescript-eslint/types': 5.60.0 '@typescript-eslint/visitor-keys': 5.60.0 - debug: 4.3.4 + debug: 4.4.0 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.3 tsutils: 3.21.0(typescript@5.2.2) optionalDependencies: typescript: 5.2.2 @@ -7968,10 +7981,10 @@ snapshots: dependencies: '@typescript-eslint/types': 6.9.1 '@typescript-eslint/visitor-keys': 6.9.1 - debug: 4.3.4 + debug: 4.4.0 globby: 11.1.0 is-glob: 4.0.3 - semver: 7.5.4 + semver: 7.6.3 ts-api-utils: 1.0.1(typescript@5.2.2) optionalDependencies: typescript: 5.2.2 @@ -7988,7 +8001,7 @@ snapshots: '@typescript-eslint/typescript-estree': 5.60.0(typescript@5.2.2) eslint: 8.47.0 eslint-scope: 5.1.1 - semver: 7.5.4 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -8002,7 +8015,7 @@ snapshots: '@typescript-eslint/types': 6.4.1 '@typescript-eslint/typescript-estree': 6.4.1(typescript@5.2.2) eslint: 8.47.0 - semver: 7.5.4 + semver: 7.6.3 transitivePeerDependencies: - supports-color - typescript @@ -8049,15 +8062,15 @@ snapshots: dependencies: '@unhead/schema': 0.5.1 - '@unhead/vue@0.5.1(typescript@5.2.2)(vue@3.3.4)': + '@unhead/vue@0.5.1(vue@3.3.4)': dependencies: '@unhead/dom': 0.5.1 '@unhead/schema': 0.5.1 - '@vueuse/shared': 12.0.0(typescript@5.2.2) + '@vueuse/shared': 10.3.0(vue@3.3.4) unhead: 0.5.1 vue: 3.3.4 transitivePeerDependencies: - - typescript + - '@vue/composition-api' '@unocss/astro@0.65.1(rollup@2.79.2)(vite@4.4.9(@types/node@18.15.11)(less@4.1.3)(terser@5.37.0))(vue@3.3.4)': dependencies: @@ -8341,14 +8354,6 @@ snapshots: source-map-js: 1.0.2 optional: true - '@vue/compiler-core@3.5.13': - dependencies: - '@babel/parser': 7.26.3 - '@vue/shared': 3.5.13 - entities: 4.5.0 - estree-walker: 2.0.2 - source-map-js: 1.2.1 - '@vue/compiler-dom@3.2.47': dependencies: '@vue/compiler-core': 3.2.47 @@ -8365,11 +8370,6 @@ snapshots: '@vue/shared': 3.3.7 optional: true - '@vue/compiler-dom@3.5.13': - dependencies: - '@vue/compiler-core': 3.5.13 - '@vue/shared': 3.5.13 - '@vue/compiler-sfc@3.2.47': dependencies: '@babel/parser': 7.21.4 @@ -8396,18 +8396,6 @@ snapshots: postcss: 8.4.28 source-map-js: 1.0.2 - '@vue/compiler-sfc@3.5.13': - dependencies: - '@babel/parser': 7.26.3 - '@vue/compiler-core': 3.5.13 - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 - estree-walker: 2.0.2 - magic-string: 0.30.15 - postcss: 8.4.49 - source-map-js: 1.2.1 - '@vue/compiler-ssr@3.2.47': dependencies: '@vue/compiler-dom': 3.2.47 @@ -8424,11 +8412,6 @@ snapshots: '@vue/shared': 3.3.7 optional: true - '@vue/compiler-ssr@3.5.13': - dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/shared': 3.5.13 - '@vue/devtools-api@6.5.0': {} '@vue/language-core@1.8.1(typescript@5.2.2)': @@ -8464,33 +8447,17 @@ snapshots: dependencies: '@vue/shared': 3.3.4 - '@vue/reactivity@3.5.13': - dependencies: - '@vue/shared': 3.5.13 - '@vue/runtime-core@3.3.4': dependencies: '@vue/reactivity': 3.3.4 '@vue/shared': 3.3.4 - '@vue/runtime-core@3.5.13': - dependencies: - '@vue/reactivity': 3.5.13 - '@vue/shared': 3.5.13 - '@vue/runtime-dom@3.3.4': dependencies: '@vue/runtime-core': 3.3.4 '@vue/shared': 3.3.4 csstype: 3.1.2 - '@vue/runtime-dom@3.5.13': - dependencies: - '@vue/reactivity': 3.5.13 - '@vue/runtime-core': 3.5.13 - '@vue/shared': 3.5.13 - csstype: 3.1.3 - '@vue/server-renderer@3.3.4(vue@3.3.4)': dependencies: '@vue/compiler-ssr': 3.3.4 @@ -8504,12 +8471,6 @@ snapshots: vue: 3.3.4 optional: true - '@vue/server-renderer@3.5.13(vue@3.5.13(typescript@5.2.2))': - dependencies: - '@vue/compiler-ssr': 3.5.13 - '@vue/shared': 3.5.13 - vue: 3.5.13(typescript@5.2.2) - '@vue/shared@3.2.47': {} '@vue/shared@3.3.4': {} @@ -8517,8 +8478,6 @@ snapshots: '@vue/shared@3.3.7': optional: true - '@vue/shared@3.5.13': {} - '@vue/test-utils@2.3.2(vue@3.3.4)': dependencies: js-beautify: 1.14.6 @@ -8546,33 +8505,26 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/head@1.0.0(typescript@5.2.2)(vue@3.3.4)': + '@vueuse/head@1.0.0(vue@3.3.4)': dependencies: '@unhead/schema': 0.5.1 '@unhead/ssr': 0.5.1 - '@unhead/vue': 0.5.1(typescript@5.2.2)(vue@3.3.4) + '@unhead/vue': 0.5.1(vue@3.3.4) vue: 3.3.4 transitivePeerDependencies: - - typescript + - '@vue/composition-api' '@vueuse/metadata@10.3.0': {} '@vueuse/router@10.0.0(vue-router@4.1.6(vue@3.3.4))(vue@3.3.4)': dependencies: - '@vueuse/shared': 10.0.0(vue@3.3.4) + '@vueuse/shared': 10.3.0(vue@3.3.4) vue-demi: 0.14.5(vue@3.3.4) vue-router: 4.1.6(vue@3.3.4) transitivePeerDependencies: - '@vue/composition-api' - vue - '@vueuse/shared@10.0.0(vue@3.3.4)': - dependencies: - vue-demi: 0.14.5(vue@3.3.4) - transitivePeerDependencies: - - '@vue/composition-api' - - vue - '@vueuse/shared@10.3.0(vue@3.3.4)': dependencies: vue-demi: 0.14.5(vue@3.3.4) @@ -8580,12 +8532,6 @@ snapshots: - '@vue/composition-api' - vue - '@vueuse/shared@12.0.0(typescript@5.2.2)': - dependencies: - vue: 3.5.13(typescript@5.2.2) - transitivePeerDependencies: - - typescript - '@zhead/schema@1.0.0-beta.13': {} abab@2.0.6: {} @@ -8780,7 +8726,7 @@ snapshots: builtins@5.0.1: dependencies: - semver: 7.5.4 + semver: 7.6.3 bundle-require@5.0.0(esbuild@0.23.1): dependencies: @@ -9022,6 +8968,8 @@ snapshots: dependencies: browserslist: 4.24.3 + core-util-is@1.0.3: {} + country-code-lookup@0.1.0: {} crelt@1.0.6: {} @@ -9084,8 +9032,6 @@ snapshots: csstype@3.1.2: {} - csstype@3.1.3: {} - dash-get@1.0.2: {} data-uri-to-buffer@4.0.1: {} @@ -10071,6 +10017,8 @@ snapshots: image-size@0.5.5: optional: true + immediate@3.0.6: {} + import-fresh@3.3.0: dependencies: parent-module: 1.0.1 @@ -10293,6 +10241,8 @@ snapshots: dependencies: is-docker: 2.2.1 + isarray@1.0.0: {} + isarray@2.0.5: {} isexe@2.0.0: {} @@ -10412,6 +10362,13 @@ snapshots: jsonpointer@5.0.1: {} + jszip@3.10.1: + dependencies: + lie: 3.3.0 + pako: 1.0.11 + readable-stream: 2.3.8 + setimmediate: 1.0.5 + jwt-decode@3.1.2: {} kind-of@6.0.3: {} @@ -10443,6 +10400,10 @@ snapshots: libphonenumber-js@1.10.28: {} + lie@3.3.0: + dependencies: + immediate: 3.0.6 + lines-and-columns@1.2.4: {} linkify-it@4.0.1: @@ -10753,7 +10714,7 @@ snapshots: normalize-package-data@2.5.0: dependencies: hosted-git-info: 2.8.9 - resolve: 1.22.8 + resolve: 1.22.9 semver: 5.7.2 validate-npm-package-license: 3.0.4 @@ -10857,6 +10818,8 @@ snapshots: package-manager-detector@0.2.7: {} + pako@1.0.11: {} + param-case@2.1.1: dependencies: no-case: 2.3.2 @@ -11017,6 +10980,8 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 + process-nextick-args@2.0.1: {} + prosemirror-changeset@2.2.1: dependencies: prosemirror-transform: 1.7.3 @@ -11184,6 +11149,16 @@ snapshots: parse-json: 5.2.0 type-fest: 0.6.0 + readable-stream@2.3.8: + dependencies: + core-util-is: 1.0.3 + inherits: 2.0.4 + isarray: 1.0.0 + process-nextick-args: 2.0.1 + safe-buffer: 5.1.2 + string_decoder: 1.1.1 + util-deprecate: 1.0.2 + readable-stream@3.6.2: dependencies: inherits: 2.0.4 @@ -11267,13 +11242,7 @@ snapshots: resolve@1.22.4: dependencies: - is-core-module: 2.13.1 - path-parse: 1.0.7 - supports-preserve-symlinks-flag: 1.0.0 - - resolve@1.22.8: - dependencies: - is-core-module: 2.13.1 + is-core-module: 2.16.0 path-parse: 1.0.7 supports-preserve-symlinks-flag: 1.0.0 @@ -11322,6 +11291,8 @@ snapshots: has-symbols: 1.1.0 isarray: 2.0.5 + safe-buffer@5.1.2: {} + safe-buffer@5.2.1: {} safe-regex-test@1.1.0: @@ -11401,6 +11372,8 @@ snapshots: is-plain-object: 2.0.4 is-primitive: 3.0.1 + setimmediate@1.0.5: {} + shebang-command@2.0.0: dependencies: shebang-regex: 3.0.0 @@ -11565,6 +11538,10 @@ snapshots: define-properties: 1.2.1 es-object-atoms: 1.0.0 + string_decoder@1.1.1: + dependencies: + safe-buffer: 5.1.2 + string_decoder@1.3.0: dependencies: safe-buffer: 5.2.1 @@ -12180,16 +12157,6 @@ snapshots: '@vue/server-renderer': 3.3.4(vue@3.3.4) '@vue/shared': 3.3.4 - vue@3.5.13(typescript@5.2.2): - dependencies: - '@vue/compiler-dom': 3.5.13 - '@vue/compiler-sfc': 3.5.13 - '@vue/runtime-dom': 3.5.13 - '@vue/server-renderer': 3.5.13(vue@3.5.13(typescript@5.2.2)) - '@vue/shared': 3.5.13 - optionalDependencies: - typescript: 5.2.2 - vuedraggable@4.1.0(vue@3.3.4): dependencies: sortablejs: 1.14.0 diff --git a/src/tools/icon-generator/icon-generator.e2e.spec.ts b/src/tools/icon-generator/icon-generator.e2e.spec.ts new file mode 100644 index 0000000000..76b6a7601f --- /dev/null +++ b/src/tools/icon-generator/icon-generator.e2e.spec.ts @@ -0,0 +1,61 @@ +import { Buffer } from 'node:buffer'; +import { expect, test } from '@playwright/test'; + +const sampleSvg = ` + + +`; + +test.describe('Tool - Icon generator', () => { + test.beforeEach(async ({ page }) => { + await page.goto('/icon-generator'); + }); + + test('has correct title', async ({ page }) => { + await expect(page).toHaveTitle('Icon generator - IT Tools'); + }); + + test('shows generator controls and default state', async ({ page }) => { + await expect(page.getByText('Generate icons')).toBeVisible(); + await expect(page.getByText('Download zip')).toBeVisible(); + await expect(page.getByText('Selected output sizes: -')).toBeVisible(); + const manifestCheckbox = page.getByRole('checkbox', { name: 'Include manifest.json in ZIP' }); + await expect(manifestCheckbox).not.toBeChecked(); + + await manifestCheckbox.click(); + await expect(manifestCheckbox).not.toBeChecked(); + }); + + test('applies preset sizes and toggles manifest option with reset', async ({ page }) => { + await page.getByRole('button', { name: 'PWA' }).click(); + await expect(page.getByText('Selected output sizes: 72, 96, 128, 144, 152, 192, 384, 512')).toBeVisible(); + + const manifestCheckbox = page.getByRole('checkbox', { name: 'Include manifest.json in ZIP' }); + await manifestCheckbox.check(); + await expect(manifestCheckbox).toBeChecked(); + + await page.getByRole('button', { name: 'Clear preset sizes' }).click(); + await expect(page.getByText('Selected output sizes: -')).toBeVisible(); + await expect(manifestCheckbox).not.toBeChecked(); + + await manifestCheckbox.click(); + await expect(manifestCheckbox).not.toBeChecked(); + }); + + test('generates icons and downloads zip', async ({ page }) => { + await page.getByRole('button', { name: 'PWA' }).click(); + await page.locator('input[type="file"]').setInputFiles({ + name: 'source.svg', + mimeType: 'image/svg+xml', + buffer: Buffer.from(sampleSvg), + }); + + await page.getByRole('button', { name: 'Generate icons' }).click(); + await expect(page.getByText('Generated icons (8)')).toBeVisible(); + + const downloadPromise = page.waitForEvent('download'); + await page.getByRole('button', { name: 'Download zip' }).click(); + const download = await downloadPromise; + expect(download.suggestedFilename()).toBe('icon-icons.zip'); + }); +}); diff --git a/src/tools/icon-generator/icon-generator.service.test.ts b/src/tools/icon-generator/icon-generator.service.test.ts new file mode 100644 index 0000000000..46bfa8a0d9 --- /dev/null +++ b/src/tools/icon-generator/icon-generator.service.test.ts @@ -0,0 +1,103 @@ +import { describe, expect, it } from 'vitest'; +import { + ICON_PRESETS, + buildIconFilename, + calculateDrawArea, + createWebAppManifest, + getPresetSizes, + normalizeSizes, + sanitizeBaseName, +} from './icon-generator.service'; + +describe('icon-generator', () => { + describe('normalizeSizes', () => { + it('should remove invalid values and deduplicate sizes', () => { + expect(normalizeSizes([192, 192, 0, -1, 512.8, 72])).toEqual([72, 192, 512]); + }); + }); + + describe('getPresetSizes', () => { + it('should return merged and sorted sizes from multiple presets', () => { + const sizes = getPresetSizes(['pwa', 'android']); + + expect(sizes).toEqual([48, 72, 96, 128, 144, 152, 192, 384, 512]); + }); + + it('should include known iOS size', () => { + const sizes = getPresetSizes(['ios']); + expect(sizes).toContain(180); + expect(sizes).toContain(1024); + }); + }); + + describe('calculateDrawArea', () => { + it('should crop in cover mode for rectangular source', () => { + const drawArea = calculateDrawArea({ + sourceWidth: 400, + sourceHeight: 200, + targetSize: 100, + fitMode: 'cover', + }); + + expect(drawArea.width).toBeGreaterThan(100); + expect(drawArea.height).toBe(100); + expect(drawArea.x).toBeLessThan(0); + }); + + it('should keep full image in contain mode for rectangular source', () => { + const drawArea = calculateDrawArea({ + sourceWidth: 400, + sourceHeight: 200, + targetSize: 100, + fitMode: 'contain', + }); + + expect(drawArea.width).toBe(100); + expect(drawArea.height).toBeLessThan(100); + expect(drawArea.y).toBeGreaterThan(0); + }); + }); + + it('should expose 3 platform presets', () => { + expect(ICON_PRESETS.map(preset => preset.key)).toEqual(['pwa', 'android', 'ios']); + }); + + it('should sanitize base name and use it in icon filename', () => { + expect(sanitizeBaseName(' My App/Icon ')).toBe('My-App-Icon'); + expect(buildIconFilename(192, ' My App/Icon ')).toBe('My-App-Icon-192x192.png'); + expect(buildIconFilename(192, ' ')).toBe('icon-192x192.png'); + }); + + it('should create manifest json with provided icons', () => { + const manifestJson = createWebAppManifest({ + appName: 'my-app', + description: 'my generated icons', + icons: [ + { filename: 'my-app-192x192.png', size: 192 }, + { filename: 'my-app-512x512.png', size: 512 }, + ], + }); + + const manifest = JSON.parse(manifestJson) as { + id: string + name: string + short_name: string + description: string + lang: string + start_url: string + scope: string + icons: { src: string; sizes: string; type: string; purpose: string }[] + }; + expect(manifest.id).toBe('/'); + expect(manifest.name).toBe('my-app'); + expect(manifest.short_name).toBe('my-app'); + expect(manifest.description).toBe('my generated icons'); + expect(manifest.lang).toBe('en'); + expect(manifest.start_url).toBe('/'); + expect(manifest.scope).toBe('/'); + expect(manifest.icons).toEqual([ + { src: 'my-app-192x192.png', sizes: '192x192', type: 'image/png', purpose: 'any maskable' }, + { src: 'my-app-512x512.png', sizes: '512x512', type: 'image/png', purpose: 'any maskable' }, + ]); + }); +}); diff --git a/src/tools/icon-generator/icon-generator.service.ts b/src/tools/icon-generator/icon-generator.service.ts new file mode 100644 index 0000000000..b7b33bf09b --- /dev/null +++ b/src/tools/icon-generator/icon-generator.service.ts @@ -0,0 +1,179 @@ +export type IconPresetKey = 'pwa' | 'android' | 'ios'; +export type IconFitMode = 'cover' | 'contain'; + +export interface IconPreset { + key: IconPresetKey + sizes: number[] +} + +export interface GeneratedIcon { + size: number + filename: string + dataUrl: string +} + +export function createWebAppManifest({ + appName, + icons, + description, + startUrl = '/', + scope = '/', + lang = 'en', + backgroundColor = '#ffffff', + themeColor = '#ffffff', +}: { + appName: string + icons: { filename: string; size: number }[] + description?: string + startUrl?: string + scope?: string + lang?: string + backgroundColor?: string + themeColor?: string +}) { + return JSON.stringify({ + id: startUrl, + name: appName, + short_name: appName, + description: description ?? `${appName} icons`, + lang, + start_url: startUrl, + scope, + display: 'standalone', + background_color: backgroundColor, + theme_color: themeColor, + icons: icons.map(icon => ({ + src: icon.filename, + sizes: `${icon.size}x${icon.size}`, + type: 'image/png', + purpose: 'any maskable', + })), + }, null, 2); +} + +export const ICON_PRESETS: IconPreset[] = [ + { key: 'pwa', sizes: [72, 96, 128, 144, 152, 192, 384, 512] }, + { key: 'android', sizes: [48, 72, 96, 144, 192, 512] }, + { key: 'ios', sizes: [20, 29, 40, 58, 60, 76, 80, 87, 120, 152, 167, 180, 1024] }, +]; + +export function normalizeSizes(sizes: number[]) { + return Array.from( + new Set( + sizes + .map(size => Math.floor(size)) + .filter(size => Number.isFinite(size) && size > 0), + ), + ).sort((a, b) => a - b); +} + +export function getPresetSizes(presetKeys: IconPresetKey[]) { + const selectedSizes = ICON_PRESETS + .filter(preset => presetKeys.includes(preset.key)) + .flatMap(preset => preset.sizes); + + return normalizeSizes(selectedSizes); +} + +export function sanitizeBaseName(baseName: string) { + const normalized = baseName.trim().replace(/[<>:"/\\|?*]+/g, '-').replace(/\s+/g, '-'); + return normalized || 'icon'; +} + +export function buildIconFilename(size: number, baseName = 'icon') { + const safeBaseName = sanitizeBaseName(baseName); + return `${safeBaseName}-${size}x${size}.png`; +} + +export function calculateDrawArea({ + sourceWidth, + sourceHeight, + targetSize, + fitMode, +}: { + sourceWidth: number + sourceHeight: number + targetSize: number + fitMode: IconFitMode +}) { + const scale = fitMode === 'cover' + ? Math.max(targetSize / sourceWidth, targetSize / sourceHeight) + : Math.min(targetSize / sourceWidth, targetSize / sourceHeight); + + const drawWidth = sourceWidth * scale; + const drawHeight = sourceHeight * scale; + + return { + x: (targetSize - drawWidth) / 2, + y: (targetSize - drawHeight) / 2, + width: drawWidth, + height: drawHeight, + }; +} + +export function loadImageFromFile(file: File) { + return new Promise((resolve, reject) => { + const url = URL.createObjectURL(file); + const image = new Image(); + image.onload = () => { + URL.revokeObjectURL(url); + resolve(image); + }; + image.onerror = () => { + URL.revokeObjectURL(url); + reject(new Error('Unable to load image file.')); + }; + image.src = url; + }); +} + +export function createResizedIcon({ + image, + size, + fitMode, +}: { + image: HTMLImageElement + size: number + fitMode: IconFitMode +}) { + const canvas = document.createElement('canvas'); + canvas.width = size; + canvas.height = size; + const context = canvas.getContext('2d'); + + if (!context) { + throw new Error('Unable to get canvas context.'); + } + + const drawArea = calculateDrawArea({ + sourceWidth: image.width, + sourceHeight: image.height, + targetSize: size, + fitMode, + }); + + context.clearRect(0, 0, size, size); + context.imageSmoothingEnabled = true; + context.imageSmoothingQuality = 'high'; + context.drawImage(image, drawArea.x, drawArea.y, drawArea.width, drawArea.height); + + return canvas.toDataURL('image/png'); +} + +export function createResizedIcons({ + image, + sizes, + fitMode, + baseName, +}: { + image: HTMLImageElement + sizes: number[] + fitMode: IconFitMode + baseName?: string +}) { + return normalizeSizes(sizes).map((size): GeneratedIcon => ({ + size, + filename: buildIconFilename(size, baseName), + dataUrl: createResizedIcon({ image, size, fitMode }), + })); +} diff --git a/src/tools/icon-generator/icon-generator.vue b/src/tools/icon-generator/icon-generator.vue new file mode 100644 index 0000000000..99e1bde7ca --- /dev/null +++ b/src/tools/icon-generator/icon-generator.vue @@ -0,0 +1,267 @@ + + + + + diff --git a/src/tools/icon-generator/index.ts b/src/tools/icon-generator/index.ts new file mode 100644 index 0000000000..06b7481cba --- /dev/null +++ b/src/tools/icon-generator/index.ts @@ -0,0 +1,13 @@ +import { ArrowsShuffle } from '@vicons/tabler'; +import { defineTool } from '../tool'; +import { translate } from '@/plugins/i18n.plugin'; + +export const tool = defineTool({ + name: translate('tools.icon-generator.title'), + path: '/icon-generator', + description: translate('tools.icon-generator.description'), + keywords: ['icon', 'generator', 'pwa', 'android', 'ios', 'app icon', 'resize', 'favicon'], + component: () => import('./icon-generator.vue'), + icon: ArrowsShuffle, + createdAt: new Date('2026-02-26'), +}); diff --git a/src/tools/index.ts b/src/tools/index.ts index 388cfaf494..8954b626ac 100644 --- a/src/tools/index.ts +++ b/src/tools/index.ts @@ -1,6 +1,7 @@ import { tool as base64FileConverter } from './base64-file-converter'; import { tool as base64StringConverter } from './base64-string-converter'; import { tool as basicAuthGenerator } from './basic-auth-generator'; +import { tool as iconGenerator } from './icon-generator'; import { tool as emailNormalizer } from './email-normalizer'; import { tool as asciiTextDrawer } from './ascii-text-drawer'; @@ -141,7 +142,7 @@ export const toolsByCategory: ToolCategory[] = [ }, { name: 'Images and videos', - components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder], + components: [qrCodeGenerator, wifiQrCodeGenerator, svgPlaceholderGenerator, cameraRecorder, iconGenerator], }, { name: 'Development',