This commit is contained in:
fclef819 2026-02-26 15:15:44 +00:00 committed by GitHub
commit a6bfc0f9c9
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
10 changed files with 783 additions and 160 deletions

View file

@ -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

View file

@ -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 "<baseName>-<size>x<size>.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.

View file

@ -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"
}
}
}

281
pnpm-lock.yaml generated
View file

@ -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

View file

@ -0,0 +1,61 @@
import { Buffer } from 'node:buffer';
import { expect, test } from '@playwright/test';
const sampleSvg = `<svg xmlns="http://www.w3.org/2000/svg" width="512" height="512">
<rect width="512" height="512" fill="#3b82f6"/>
<circle cx="256" cy="256" r="140" fill="#f59e0b"/>
</svg>`;
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');
});
});

View file

@ -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' },
]);
});
});

View file

@ -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<HTMLImageElement>((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 }),
}));
}

View file

@ -0,0 +1,267 @@
<script setup lang="ts">
import JSZip from 'jszip';
import {
ICON_PRESETS,
createResizedIcons,
createWebAppManifest,
getPresetSizes,
loadImageFromFile,
normalizeSizes,
sanitizeBaseName,
} from './icon-generator.service';
import type { GeneratedIcon, IconFitMode, IconPresetKey } from './icon-generator.service';
const { t } = useI18n();
const selectedSizesModel = ref<number[]>([]);
const customSizeInput = ref<number | null>(null);
const fitMode = ref<IconFitMode>('cover');
const baseName = ref('icon');
const includeManifest = ref(false);
const sourceFile = ref<File | null>(null);
const sourcePreviewUrl = ref('');
const generatedIcons = ref<GeneratedIcon[]>([]);
const isGenerating = ref(false);
const availableSizes = computed(() =>
normalizeSizes([...ICON_PRESETS.flatMap(preset => preset.sizes), ...selectedSizesModel.value]),
);
const selectedSizes = computed(() => normalizeSizes(selectedSizesModel.value));
const isPwaPresetApplied = computed(() => isPresetApplied('pwa'));
const canGenerate = computed(() => sourceFile.value !== null && selectedSizes.value.length > 0 && !isGenerating.value);
function readFileAsDataUrl(file: File) {
return new Promise<string>((resolve, reject) => {
const reader = new FileReader();
reader.onload = () => resolve(String(reader.result || ''));
reader.onerror = () => reject(new Error('Unable to read image file.'));
reader.readAsDataURL(file);
});
}
async function onUpload(file: File) {
sourceFile.value = file;
sourcePreviewUrl.value = await readFileAsDataUrl(file);
generatedIcons.value = [];
}
function addCustomSize() {
if (customSizeInput.value == null) {
return;
}
const size = Math.floor(customSizeInput.value);
if (size <= 0) {
return;
}
selectedSizesModel.value = normalizeSizes([...selectedSizesModel.value, size]);
customSizeInput.value = null;
}
function applyPreset(presetKey: IconPresetKey) {
const sizes = getPresetSizes([presetKey]);
selectedSizesModel.value = normalizeSizes([...selectedSizesModel.value, ...sizes]);
}
function isPresetApplied(presetKey: IconPresetKey) {
const sizes = getPresetSizes([presetKey]);
return sizes.every(size => selectedSizesModel.value.includes(size));
}
function clearPresetSizes() {
const presetSizes = normalizeSizes(ICON_PRESETS.flatMap(preset => preset.sizes));
selectedSizesModel.value = selectedSizesModel.value.filter(size => !presetSizes.includes(size));
includeManifest.value = false;
}
function getPresetLabel(presetKey: IconPresetKey) {
return t(`tools.icon-generator.presetLabels.${presetKey}`);
}
async function generateIcons() {
if (!sourceFile.value) {
return;
}
isGenerating.value = true;
try {
const image = await loadImageFromFile(sourceFile.value);
generatedIcons.value = createResizedIcons({
image,
sizes: selectedSizes.value,
fitMode: fitMode.value,
baseName: baseName.value,
});
}
finally {
isGenerating.value = false;
}
}
function downloadIcon(icon: GeneratedIcon) {
const link = document.createElement('a');
link.href = icon.dataUrl;
link.download = icon.filename;
link.click();
}
async function downloadIconsAsZip() {
const zip = new JSZip();
generatedIcons.value.forEach((icon) => {
const [, base64Content] = icon.dataUrl.split(',');
if (!base64Content) {
return;
}
zip.file(icon.filename, base64Content, { base64: true });
});
if (includeManifest.value && isPwaPresetApplied.value) {
const appName = sanitizeBaseName(baseName.value);
zip.file('manifest.json', createWebAppManifest({
appName,
icons: generatedIcons.value.map(icon => ({ filename: icon.filename, size: icon.size })),
}));
}
const zipBlob = await zip.generateAsync({ type: 'blob' });
const zipUrl = URL.createObjectURL(zipBlob);
const link = document.createElement('a');
link.href = zipUrl;
link.download = `${sanitizeBaseName(baseName.value)}-icons.zip`;
link.click();
URL.revokeObjectURL(zipUrl);
}
watch(isPwaPresetApplied, (pwaEnabled) => {
if (!pwaEnabled) {
includeManifest.value = false;
}
});
</script>
<template>
<div>
<c-card :title="t('tools.icon-generator.presets')">
<n-space vertical :size="12">
<div class="flex flex-wrap gap-2">
<c-button
v-for="preset in ICON_PRESETS"
:key="preset.key"
:type="isPresetApplied(preset.key) ? 'primary' : 'default'"
@click="applyPreset(preset.key)"
>
{{ getPresetLabel(preset.key) }}
</c-button>
<c-button secondary @click="clearPresetSizes">
{{ t('tools.icon-generator.clearPresetSizes') }}
</c-button>
</div>
<c-buttons-select
v-model:value="fitMode"
:label="`${t('tools.icon-generator.fitMode')}:`"
label-position="left"
:options="[
{ label: t('tools.icon-generator.cover'), value: 'cover' },
{ label: t('tools.icon-generator.contain'), value: 'contain' },
]"
/>
<div class="flex items-center gap-2">
<n-input-number v-model:value="customSizeInput" :min="16" :max="2048" placeholder="256" />
<c-button @click="addCustomSize">
{{ t('tools.icon-generator.addSize') }}
</c-button>
</div>
<c-input-text
v-model:value="baseName"
:label="`${t('tools.icon-generator.baseName')}:`"
placeholder="icon"
label-position="left"
/>
<n-text depth="3">
{{ t('tools.icon-generator.baseNameHint') }}
</n-text>
<n-checkbox v-model:checked="includeManifest" :disabled="!isPwaPresetApplied">
{{ t('tools.icon-generator.includeManifest') }}
</n-checkbox>
<n-checkbox-group v-model:value="selectedSizesModel">
<n-space>
<n-checkbox
v-for="size in availableSizes"
:key="size"
:value="size"
:label="`${size}x${size}`"
/>
</n-space>
</n-checkbox-group>
<n-text depth="3">
{{ t('tools.icon-generator.selectedSizes') }}: {{ selectedSizes.join(', ') || '-' }}
</n-text>
</n-space>
</c-card>
<c-card>
<c-file-upload
:title="t('tools.icon-generator.uploadTitle')"
accept="image/png,image/jpeg,image/webp,image/svg+xml,image/x-icon,image/vnd.microsoft.icon"
@file-upload="onUpload"
/>
<div mt-4 flex flex-col items-center gap-3>
<n-image v-if="sourcePreviewUrl" :src="sourcePreviewUrl" width="180" />
<n-text v-else depth="3">
{{ t('tools.icon-generator.noImage') }}
</n-text>
<div flex flex-wrap justify-center gap-2>
<c-button :disabled="!canGenerate" @click="generateIcons">
{{ t('tools.icon-generator.generate') }}
</c-button>
<c-button :disabled="generatedIcons.length === 0" secondary @click="downloadIconsAsZip">
{{ t('tools.icon-generator.downloadZip') }}
</c-button>
</div>
</div>
</c-card>
<c-card v-if="generatedIcons.length > 0" :title="`${t('tools.icon-generator.output')} (${generatedIcons.length})`">
<n-grid cols="1 520:2 760:3" :x-gap="12" :y-gap="12">
<n-gi v-for="icon in generatedIcons" :key="icon.filename">
<div class="generated-item">
<n-image :src="icon.dataUrl" width="96" preview-disabled />
<div class="text-center">
<n-text strong>
{{ icon.size }}x{{ icon.size }}
</n-text>
</div>
<c-button size="small" @click="downloadIcon(icon)">
{{ t('tools.icon-generator.download') }}
</c-button>
</div>
</n-gi>
</n-grid>
</c-card>
</div>
</template>
<style lang="less" scoped>
.generated-item {
display: flex;
flex-direction: column;
align-items: center;
gap: 10px;
padding: 12px;
border: 1px solid var(--c-border-color);
border-radius: 8px;
}
</style>

View file

@ -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'),
});

View file

@ -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',