mirror of
https://github.com/CorentinTh/it-tools.git
synced 2026-10-24 06:43:40 +00:00
Merge 7f239eb30f into d505845f91
This commit is contained in:
commit
ed78470afb
9 changed files with 440 additions and 191 deletions
43
.github/workflows/e2e-tests.yml
vendored
43
.github/workflows/e2e-tests.yml
vendored
|
|
@ -6,7 +6,7 @@ on:
|
|||
- main
|
||||
jobs:
|
||||
test:
|
||||
timeout-minutes: 10
|
||||
timeout-minutes: 90
|
||||
runs-on: ubuntu-latest
|
||||
strategy:
|
||||
matrix:
|
||||
|
|
@ -23,10 +23,10 @@ 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"]' package.json)" >> "$GITHUB_OUTPUT"
|
||||
|
||||
- name: Install dependencies
|
||||
run: pnpm install
|
||||
run: pnpm install --ignore-scripts
|
||||
|
||||
- name: Build app
|
||||
run: pnpm build
|
||||
|
|
@ -40,8 +40,41 @@ jobs:
|
|||
${{ runner.os }}-playwright-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}-
|
||||
${{ runner.os }}-playwright-
|
||||
|
||||
- name: Install Playwright Browsers
|
||||
run: pnpm exec playwright install --with-deps
|
||||
# --with-deps bundles an apt-get run for the full browser dependency
|
||||
# set; the ubuntu archive is intermittently very slow, so it gets its
|
||||
# own step with a generous timeout, retries, and an apt-cache entry so
|
||||
# a one-time successful run seeds later ones
|
||||
- name: Restore apt cache for Playwright dependencies
|
||||
uses: actions/cache@v3
|
||||
with:
|
||||
path: ~/apt-playwright-cache
|
||||
key: ${{ runner.os }}-apt-${{ steps.playwright-version.outputs.PLAYWRIGHT_VERSION }}
|
||||
|
||||
- name: Install Playwright system dependencies
|
||||
timeout-minutes: 45
|
||||
run: |
|
||||
for attempt in 1 2 3 4 5; do
|
||||
if timeout 1500 ./node_modules/.bin/playwright install-deps; then
|
||||
exit 0
|
||||
fi
|
||||
echo "playwright install-deps attempt $attempt failed (attempt timeout 25 min), retrying in 20s"
|
||||
sleep 20
|
||||
done
|
||||
exit 1
|
||||
|
||||
- name: Install Playwright browsers
|
||||
timeout-minutes: 10
|
||||
env:
|
||||
PLAYWRIGHT_DOWNLOAD_CONNECTION_TIMEOUT: 60000
|
||||
run: |
|
||||
for attempt in 1 2 3; do
|
||||
if ./node_modules/.bin/playwright install; then
|
||||
exit 0
|
||||
fi
|
||||
echo "playwright install attempt $attempt failed, retrying in 15s"
|
||||
sleep 15
|
||||
done
|
||||
exit 1
|
||||
|
||||
- name: Run Playwright tests
|
||||
run: pnpm run test:e2e --shard=${{ matrix.shard }}
|
||||
|
|
|
|||
|
|
@ -109,7 +109,7 @@
|
|||
"@antfu/eslint-config": "^0.41.0",
|
||||
"@iconify-json/mdi": "^1.1.50",
|
||||
"@intlify/unplugin-vue-i18n": "^2.0.0",
|
||||
"@playwright/test": "^1.32.3",
|
||||
"@playwright/test": "^1.62.1",
|
||||
"@rushstack/eslint-patch": "^1.2.0",
|
||||
"@tsconfig/node18": "^18.2.0",
|
||||
"@types/bcryptjs": "^2.4.2",
|
||||
|
|
@ -149,5 +149,10 @@
|
|||
"vitest": "^0.34.0",
|
||||
"workbox-window": "^7.0.0",
|
||||
"zx": "^7.2.1"
|
||||
},
|
||||
"pnpm": {
|
||||
"overrides": {
|
||||
"@vueuse/shared": "10.3.0"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
|||
260
pnpm-lock.yaml
generated
260
pnpm-lock.yaml
generated
|
|
@ -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)
|
||||
|
|
@ -220,8 +223,8 @@ importers:
|
|||
specifier: ^2.0.0
|
||||
version: 2.0.0(rollup@2.79.2)(vue-i18n@9.9.1(vue@3.3.4))
|
||||
'@playwright/test':
|
||||
specifier: ^1.32.3
|
||||
version: 1.32.3
|
||||
specifier: ^1.62.1
|
||||
version: 1.63.0
|
||||
'@rushstack/eslint-patch':
|
||||
specifier: ^1.2.0
|
||||
version: 1.2.0
|
||||
|
|
@ -332,7 +335,7 @@ importers:
|
|||
version: 4.0.0
|
||||
vitest:
|
||||
specifier: ^0.34.0
|
||||
version: 0.34.0(jsdom@22.0.0)(less@4.1.3)(terser@5.37.0)
|
||||
version: 0.34.0(jsdom@22.0.0)(less@4.1.3)(playwright@1.63.0)(terser@5.37.0)
|
||||
workbox-window:
|
||||
specifier: ^7.0.0
|
||||
version: 7.0.0
|
||||
|
|
@ -356,17 +359,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 +1470,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 +1478,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==}
|
||||
|
|
@ -1626,9 +1634,9 @@ packages:
|
|||
resolution: {integrity: sha512-wfzX8kc1PMyUILA+1Z/EqoE4UCXGy0iRGMhPwdfae1+f0OXlLqCk+By+aMzgJBzR9AzS4CDizioG6Ss1gvAFJw==}
|
||||
engines: {node: ^12.20.0 || ^14.18.0 || >=16.0.0}
|
||||
|
||||
'@playwright/test@1.32.3':
|
||||
resolution: {integrity: sha512-BvWNvK0RfBriindxhLVabi8BRe3X0J9EVjKlcmhxjg4giWBD/xleLcg2dz7Tx0agu28rczjNIPQWznwzDwVsZQ==}
|
||||
engines: {node: '>=14'}
|
||||
'@playwright/test@1.63.0':
|
||||
resolution: {integrity: sha512-oxMK4vllB9RK5NQ2l1pq1IfOf2AvnEuj/vYGDj0H2nMtmtZpKtCwt/l00GEO6xjGfpBNAvjovvYdCm50dRQkpQ==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
'@polka/url@1.0.0-next.28':
|
||||
|
|
@ -2303,9 +2311,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 +2320,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 +2335,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 +2355,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 +2371,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 +2380,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 +2407,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 +2526,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 +2773,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==}
|
||||
|
|
@ -2850,6 +2825,7 @@ packages:
|
|||
|
||||
crypto-js@4.1.1:
|
||||
resolution: {integrity: sha512-o2JlM7ydqd3Qk9CA0L4NL6mTzU2sdx96a+oOfPu8Mkl/PK51vSyoi8/rQ8NknZtk44vq15lmhAj9CIAGwgeWKw==}
|
||||
deprecated: Active development of CryptoJS has been discontinued. This library is no longer maintained.
|
||||
|
||||
crypto-random-string@2.0.0:
|
||||
resolution: {integrity: sha512-v1plID3y9r/lPhviJ1wrXpLeyUIGAZ2SHNYTEapm7/8A9nLPoyvVp3RK/EPFqn5kEznyWgYZNsRtYYIWbuG8KA==}
|
||||
|
|
@ -2896,9 +2872,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 +3027,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 +3220,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 +3246,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 +3313,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:
|
||||
|
|
@ -3499,11 +3476,6 @@ packages:
|
|||
fs.realpath@1.0.0:
|
||||
resolution: {integrity: sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==}
|
||||
|
||||
fsevents@2.3.2:
|
||||
resolution: {integrity: sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
os: [darwin]
|
||||
|
||||
fsevents@2.3.3:
|
||||
resolution: {integrity: sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==}
|
||||
engines: {node: ^8.16.0 || ^10.6.0 || >=11.0.0}
|
||||
|
|
@ -3566,10 +3538,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==}
|
||||
|
|
@ -3758,6 +3732,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==}
|
||||
|
|
@ -4172,6 +4147,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 +4356,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==}
|
||||
|
|
@ -4610,10 +4587,16 @@ 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==}
|
||||
engines: {node: '>=14'}
|
||||
playwright-core@1.63.0:
|
||||
resolution: {integrity: sha512-rYCsBF/M5HjUch52bbtVONEFjv6Xu8sm8h72dNlR5bzIE1fvC/bxgspzkjSfU+MweEMmPM8KJebG6nnyxo5mCg==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
playwright@1.63.0:
|
||||
resolution: {integrity: sha512-+7ziBLidS4NaNCdt57SUDT+wYmmd5fmiQejUic/kb+YsYSCPyOOE9sebzMjNmQrsnNpDJqd4WHvV/8lfKfUDUg==}
|
||||
engines: {node: '>=20'}
|
||||
hasBin: true
|
||||
|
||||
pluralize@8.0.0:
|
||||
|
|
@ -4868,10 +4851,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 +4873,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:
|
||||
|
|
@ -5071,9 +5051,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==}
|
||||
|
|
@ -5544,6 +5526,7 @@ packages:
|
|||
|
||||
uuid@9.0.0:
|
||||
resolution: {integrity: sha512-MXcSTerfPa4uqyzStbRoTgt5XIe3x5+42+q1sDuy3R5MDk66URdLMOZe5aPX/SQd+kuYAh0FdP/pO28IkQyTeg==}
|
||||
deprecated: uuid@10 and below is no longer supported. For ESM codebases, update to uuid@latest. For CommonJS codebases, use uuid@11 (but be aware this version will likely be deprecated in 2028).
|
||||
hasBin: true
|
||||
|
||||
validate-npm-package-license@3.0.4:
|
||||
|
|
@ -5569,6 +5552,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 +5659,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 +5686,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 +5731,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 +6095,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 +7008,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 +7379,7 @@ snapshots:
|
|||
|
||||
'@linaria/logger@4.0.0':
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
picocolors: 1.0.0
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
|
@ -7462,12 +7440,9 @@ snapshots:
|
|||
tiny-glob: 0.2.9
|
||||
tslib: 2.5.0
|
||||
|
||||
'@playwright/test@1.32.3':
|
||||
'@playwright/test@1.63.0':
|
||||
dependencies:
|
||||
'@types/node': 18.15.11
|
||||
playwright-core: 1.32.3
|
||||
optionalDependencies:
|
||||
fsevents: 2.3.2
|
||||
playwright: 1.63.0
|
||||
|
||||
'@polka/url@1.0.0-next.28': {}
|
||||
|
||||
|
|
@ -7922,7 +7897,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 +7915,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
|
||||
|
|
@ -7954,10 +7929,10 @@ snapshots:
|
|||
dependencies:
|
||||
'@typescript-eslint/types': 6.4.1
|
||||
'@typescript-eslint/visitor-keys': 6.4.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
|
||||
|
|
@ -7968,10 +7943,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 +7963,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 +7977,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 +8024,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 +8316,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 +8332,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 +8358,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 +8374,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 +8409,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 +8433,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 +8440,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 +8467,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 +8494,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 +8688,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:
|
||||
|
|
@ -9084,8 +8992,6 @@ snapshots:
|
|||
|
||||
csstype@3.1.2: {}
|
||||
|
||||
csstype@3.1.3: {}
|
||||
|
||||
dash-get@1.0.2: {}
|
||||
|
||||
data-uri-to-buffer@4.0.1: {}
|
||||
|
|
@ -9817,9 +9723,6 @@ snapshots:
|
|||
|
||||
fs.realpath@1.0.0: {}
|
||||
|
||||
fsevents@2.3.2:
|
||||
optional: true
|
||||
|
||||
fsevents@2.3.3:
|
||||
optional: true
|
||||
|
||||
|
|
@ -10753,7 +10656,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
|
||||
|
||||
|
|
@ -10972,7 +10875,11 @@ snapshots:
|
|||
|
||||
plausible-tracker@0.3.8: {}
|
||||
|
||||
playwright-core@1.32.3: {}
|
||||
playwright-core@1.63.0: {}
|
||||
|
||||
playwright@1.63.0:
|
||||
dependencies:
|
||||
playwright-core: 1.63.0
|
||||
|
||||
pluralize@8.0.0: {}
|
||||
|
||||
|
|
@ -11271,12 +11178,6 @@ snapshots:
|
|||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.8:
|
||||
dependencies:
|
||||
is-core-module: 2.13.1
|
||||
path-parse: 1.0.7
|
||||
supports-preserve-symlinks-flag: 1.0.0
|
||||
|
||||
resolve@1.22.9:
|
||||
dependencies:
|
||||
is-core-module: 2.16.0
|
||||
|
|
@ -12073,7 +11974,7 @@ snapshots:
|
|||
less: 4.1.3
|
||||
terser: 5.37.0
|
||||
|
||||
vitest@0.34.0(jsdom@22.0.0)(less@4.1.3)(terser@5.37.0):
|
||||
vitest@0.34.0(jsdom@22.0.0)(less@4.1.3)(playwright@1.63.0)(terser@5.37.0):
|
||||
dependencies:
|
||||
'@types/chai': 4.3.5
|
||||
'@types/chai-subset': 1.3.3
|
||||
|
|
@ -12101,6 +12002,7 @@ snapshots:
|
|||
why-is-node-running: 2.2.2
|
||||
optionalDependencies:
|
||||
jsdom: 22.0.0
|
||||
playwright: 1.63.0
|
||||
transitivePeerDependencies:
|
||||
- less
|
||||
- lightningcss
|
||||
|
|
@ -12125,14 +12027,14 @@ snapshots:
|
|||
|
||||
vue-eslint-parser@9.3.1(eslint@8.47.0):
|
||||
dependencies:
|
||||
debug: 4.3.4
|
||||
debug: 4.4.0
|
||||
eslint: 8.47.0
|
||||
eslint-scope: 7.2.2
|
||||
eslint-visitor-keys: 3.4.3
|
||||
espree: 9.6.1
|
||||
esquery: 1.5.0
|
||||
lodash: 4.17.21
|
||||
semver: 7.5.4
|
||||
semver: 7.6.3
|
||||
transitivePeerDependencies:
|
||||
- supports-color
|
||||
|
||||
|
|
@ -12180,16 +12082,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
|
||||
|
|
|
|||
|
|
@ -55,6 +55,7 @@ import { tool as chronometer } from './chronometer';
|
|||
import { tool as colorConverter } from './color-converter';
|
||||
import { tool as crontabGenerator } from './crontab-generator';
|
||||
import { tool as dateTimeConverter } from './date-time-converter';
|
||||
import { tool as timezoneConverter } from './timezone-converter';
|
||||
import { tool as deviceInformation } from './device-information';
|
||||
import { tool as cypher } from './encryption';
|
||||
import { tool as etaCalculator } from './eta-calculator';
|
||||
|
|
@ -172,7 +173,7 @@ export const toolsByCategory: ToolCategory[] = [
|
|||
},
|
||||
{
|
||||
name: 'Measurement',
|
||||
components: [chronometer, temperatureConverter, benchmarkBuilder],
|
||||
components: [chronometer, temperatureConverter, benchmarkBuilder, timezoneConverter],
|
||||
},
|
||||
{
|
||||
name: 'Text',
|
||||
|
|
|
|||
12
src/tools/timezone-converter/index.ts
Normal file
12
src/tools/timezone-converter/index.ts
Normal file
|
|
@ -0,0 +1,12 @@
|
|||
import { World } from '@vicons/tabler';
|
||||
import { defineTool } from '../tool';
|
||||
|
||||
export const tool = defineTool({
|
||||
name: 'Timezone converter',
|
||||
path: '/timezone-converter',
|
||||
description: 'Convert a date and time between IANA timezones, honoring DST rules',
|
||||
keywords: ['timezone', 'time', 'zone', 'converter', 'dst', 'utc', 'gmt', 'iana'],
|
||||
component: () => import('./timezone-converter.vue'),
|
||||
icon: World,
|
||||
createdAt: new Date('2026-10-01'),
|
||||
});
|
||||
|
|
@ -0,0 +1,79 @@
|
|||
import { expect, it } from 'vitest';
|
||||
import {
|
||||
convertBetweenTimezones,
|
||||
getTimeZoneOffsetMs,
|
||||
listTimezones,
|
||||
offsetLabel,
|
||||
} from './timezone-converter.models';
|
||||
|
||||
it('offset matches Intl shortOffset rendering across all zones and dense instants', () => {
|
||||
const zones = listTimezones().filter(z => z !== 'UTC');
|
||||
const now = Date.now();
|
||||
let checked = 0;
|
||||
for (const zone of zones) {
|
||||
const formatter = new Intl.DateTimeFormat('en-US', { timeZone: zone, timeZoneName: 'shortOffset' });
|
||||
for (let dayOffset = -400; dayOffset <= 400; dayOffset += 7) {
|
||||
for (const hour of [0, 3, 6, 9, 12, 15, 18, 21]) {
|
||||
const date = new Date(now + dayOffset * 86400_000 + hour * 3600_000);
|
||||
const mine = offsetLabel(getTimeZoneOffsetMs(date, zone));
|
||||
const intlName = formatter.formatToParts(date).find(p => p.type === 'timeZoneName')!.value;
|
||||
// The tool styles zero offsets as 'UTC' while en-US shortOffset
|
||||
// renders 'GMT+0'; normalize before comparing.
|
||||
const normalized = /GMT\+?0$/.test(intlName) ? 'GMT' : intlName;
|
||||
const expected = normalized === 'GMT' ? 'UTC' : `UTC${normalized.slice(3)}`;
|
||||
expect(mine, `${zone} @ ${date.toISOString()}`).toBe(expected);
|
||||
checked++;
|
||||
}
|
||||
}
|
||||
}
|
||||
expect(checked).toBeGreaterThan(300000);
|
||||
});
|
||||
|
||||
it('A to B to A roundtrip is lossless across zone pairs', () => {
|
||||
const zones = listTimezones();
|
||||
const now = Date.now();
|
||||
let roundtrips = 0;
|
||||
for (const fromTz of zones) {
|
||||
for (const toTz of zones.slice(0, 8)) {
|
||||
if (fromTz === toTz) {
|
||||
continue;
|
||||
}
|
||||
const instant = new Date(now + ((roundtrips * 3_600_000 * 7) % (400 * 86400_000)));
|
||||
const parts = new Intl.DateTimeFormat('en-CA', {
|
||||
timeZone: fromTz,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
hourCycle: 'h23',
|
||||
}).formatToParts(instant);
|
||||
const v = Object.fromEntries(parts.map(p => [p.type, p.value]));
|
||||
const naive = `${v.year}-${v.month}-${v.day} ${v.hour}:${v.minute}:${v.second}`;
|
||||
const forward = convertBetweenTimezones({ dateTime: naive, fromTz, toTz });
|
||||
const back = convertBetweenTimezones({ dateTime: forward.formatted, fromTz: toTz, toTz: fromTz });
|
||||
expect(back.formatted, `${fromTz}->${toTz} (forward ${forward.formatted})`).toBe(naive);
|
||||
roundtrips++;
|
||||
}
|
||||
}
|
||||
expect(roundtrips).toBeGreaterThan(1000);
|
||||
});
|
||||
|
||||
it('resolves wall times near midnight-adjacent fall-back transitions (Malta regression)', () => {
|
||||
// EU fall-back happens at 01:00 UTC, so the offset at the naive time
|
||||
// treated as UTC (CET, +1) differs from the offset at the true instant
|
||||
// (CEST, +2). The single-pass algorithm parsed this 1 hour late:
|
||||
// 2027-10-31 01:05:27 CEST is instant 2027-10-30 23:05:27Z, which in
|
||||
// UTC+3 Asmera renders as 2027-10-31 02:05:27.
|
||||
const converted = convertBetweenTimezones({ dateTime: '2027-10-31 01:05:27', fromTz: 'Europe/Malta', toTz: 'Africa/Asmera' });
|
||||
expect(converted.formatted).toBe('2027-10-31 02:05:27');
|
||||
expect(converted.sourceOffset).toBe('UTC+2');
|
||||
});
|
||||
|
||||
it('gap and fold instants resolve to the valid neighborhood', () => {
|
||||
const gap = convertBetweenTimezones({ dateTime: '2026-03-08 02:30:00', fromTz: 'America/New_York', toTz: 'UTC' });
|
||||
expect(gap.formatted).toMatch(/^2026-03-08 0[6-7]:30:00$/);
|
||||
const fold = convertBetweenTimezones({ dateTime: '2026-11-01 01:30:00', fromTz: 'America/New_York', toTz: 'UTC' });
|
||||
expect(fold.formatted).toMatch(/^2026-11-01 0[5-6]:30:00$/);
|
||||
});
|
||||
|
|
@ -0,0 +1,44 @@
|
|||
import { describe, expect, it } from 'vitest';
|
||||
import { convertBetweenTimezones, listTimezones } from './timezone-converter.models';
|
||||
|
||||
describe('timezone-converter', () => {
|
||||
const convert = (dateTime: string, fromTz: string, toTz: string) =>
|
||||
convertBetweenTimezones({ dateTime, fromTz, toTz });
|
||||
|
||||
it('converts UTC to a fixed-offset zone', () => {
|
||||
const result = convert('2024-01-15 12:00:00', 'UTC', 'Asia/Shanghai');
|
||||
expect(result.formatted).toBe('2024-01-15 20:00:00');
|
||||
expect(result.targetOffset).toBe('UTC+8');
|
||||
expect(result.sourceOffset).toBe('UTC');
|
||||
});
|
||||
|
||||
it('honors US DST in summer (EDT = UTC-4)', () => {
|
||||
const result = convert('2024-07-01 12:00:00', 'America/New_York', 'UTC');
|
||||
expect(result.formatted).toBe('2024-07-01 16:00:00');
|
||||
expect(result.sourceOffset).toBe('UTC-4');
|
||||
});
|
||||
|
||||
it('honors US standard time in winter (EST = UTC-5)', () => {
|
||||
const result = convert('2024-01-01 12:00:00', 'America/New_York', 'UTC');
|
||||
expect(result.formatted).toBe('2024-01-01 17:00:00');
|
||||
expect(result.sourceOffset).toBe('UTC-5');
|
||||
});
|
||||
|
||||
it('handles half-hour offset zones', () => {
|
||||
const result = convert('2024-03-10 09:30:00', 'UTC', 'Asia/Kolkata');
|
||||
expect(result.targetOffset).toBe('UTC+5:30');
|
||||
expect(result.formatted).toBe('2024-03-10 15:00:00');
|
||||
});
|
||||
|
||||
it('rejects bad datetimes and unknown zones', () => {
|
||||
expect(() => convertBetweenTimezones({ dateTime: 'nope', fromTz: 'UTC', toTz: 'UTC' })).toThrow('Invalid date/time');
|
||||
expect(() => convertBetweenTimezones({ dateTime: '2024-01-01 10:00:00', fromTz: 'Nowhere/Bad', toTz: 'UTC' })).toThrow('Unknown source timezone');
|
||||
expect(() => convertBetweenTimezones({ dateTime: '2024-01-01 10:00:00', fromTz: 'UTC', toTz: 'Nowhere/Bad' })).toThrow('Unknown target timezone');
|
||||
});
|
||||
|
||||
it('lists the IANA timezones on modern runtimes', () => {
|
||||
const zones = listTimezones();
|
||||
expect(zones).toContain('UTC');
|
||||
expect(zones).toContain('Asia/Shanghai');
|
||||
});
|
||||
});
|
||||
114
src/tools/timezone-converter/timezone-converter.models.ts
Normal file
114
src/tools/timezone-converter/timezone-converter.models.ts
Normal file
|
|
@ -0,0 +1,114 @@
|
|||
export interface TimezoneConversion {
|
||||
/** The instant, formatted in the target timezone. */
|
||||
formatted: string
|
||||
/** e.g. 'UTC+8' or 'UTC-4:30'. */
|
||||
targetOffset: string
|
||||
sourceOffset: string
|
||||
}
|
||||
|
||||
/** Offset of a timezone at a given instant, in milliseconds. Exported for
|
||||
* the differential suite — mirroring it there trips SonarCloud's copy-paste
|
||||
* detection and would leave the sweep testing a copy, not the real code. */
|
||||
export function getTimeZoneOffsetMs(date: Date, timeZone: string): number {
|
||||
const parts = new Intl.DateTimeFormat('en-US', {
|
||||
timeZone,
|
||||
hour12: false,
|
||||
year: 'numeric',
|
||||
month: '2-digit',
|
||||
day: '2-digit',
|
||||
hour: '2-digit',
|
||||
minute: '2-digit',
|
||||
second: '2-digit',
|
||||
}).formatToParts(date);
|
||||
|
||||
const values = Object.fromEntries(parts.map(part => [part.type, part.value]));
|
||||
const asUtc = Date.UTC(
|
||||
Number(values.year),
|
||||
Number(values.month) - 1,
|
||||
Number(values.day),
|
||||
Number(values.hour) % 24,
|
||||
Number(values.minute),
|
||||
Number(values.second),
|
||||
);
|
||||
return asUtc - date.getTime();
|
||||
}
|
||||
|
||||
export function offsetLabel(offsetMs: number): string {
|
||||
// getTimeZoneOffsetMs carries sub-second residue from Date milliseconds;
|
||||
// round to whole minutes BEFORE the sign test or a -363 ms residue on a
|
||||
// zero-offset zone renders as 'UTC-0'.
|
||||
const totalMinutes = Math.round(offsetMs / 60000);
|
||||
if (totalMinutes === 0) {
|
||||
return 'UTC';
|
||||
}
|
||||
const sign = totalMinutes < 0 ? '-' : '+';
|
||||
const abs = Math.abs(totalMinutes);
|
||||
const hours = Math.floor(abs / 60);
|
||||
const minutes = abs % 60;
|
||||
const minutesLabel = minutes > 0 ? `:${String(minutes).padStart(2, '0')}` : '';
|
||||
return `UTC${sign}${hours}${minutes > 0 ? minutesLabel : ''}`;
|
||||
}
|
||||
|
||||
/** Interpret a naive local datetime ('YYYY-MM-DD HH:mm:ss') in `fromTz` and
|
||||
* render it in `toTz`, honoring each zone's DST rules. */
|
||||
export function convertBetweenTimezones({ dateTime, fromTz, toTz }: { dateTime: string; fromTz: string; toTz: string }): TimezoneConversion {
|
||||
const normalized = dateTime.trim().replace(' ', 'T');
|
||||
const assumedUtc = Date.parse(`${normalized}Z`);
|
||||
if (Number.isNaN(assumedUtc)) {
|
||||
throw new TypeError('Invalid date/time (expected YYYY-MM-DD HH:mm:ss)');
|
||||
}
|
||||
|
||||
const invalidZone = (name: string) => {
|
||||
try {
|
||||
// constructing validates the zone identifier
|
||||
return !Intl.DateTimeFormat('en-US', { timeZone: name }).resolvedOptions().timeZone;
|
||||
}
|
||||
catch {
|
||||
return true;
|
||||
}
|
||||
};
|
||||
if (invalidZone(fromTz)) {
|
||||
throw new Error(`Unknown source timezone: ${fromTz}`);
|
||||
}
|
||||
if (invalidZone(toTz)) {
|
||||
throw new Error(`Unknown target timezone: ${toTz}`);
|
||||
}
|
||||
|
||||
// Resolve the source-zone offset by fixed-point iteration. A single pass
|
||||
// measures the offset at the naive time treated as UTC, which is wrong
|
||||
// whenever the true instant sits across a nearby transition (e.g. EU
|
||||
// fall-back at 01:00 UTC makes wall 01:05 CEST correspond to the previous
|
||||
// day). Iterate until the offset is self-consistent; realizable wall
|
||||
// times converge in ≤ 2 steps, and gap/fold times settle on one of the
|
||||
// adjacent offsets.
|
||||
let sourceOffset = getTimeZoneOffsetMs(new Date(assumedUtc), fromTz);
|
||||
let trueUtc = assumedUtc - sourceOffset;
|
||||
for (let i = 0; i < 3; i++) {
|
||||
const nextOffset = getTimeZoneOffsetMs(new Date(trueUtc), fromTz);
|
||||
if (nextOffset === sourceOffset) {
|
||||
break;
|
||||
}
|
||||
sourceOffset = nextOffset;
|
||||
trueUtc = assumedUtc - sourceOffset;
|
||||
}
|
||||
const targetOffset = getTimeZoneOffsetMs(new Date(trueUtc), toTz);
|
||||
|
||||
const target = new Date(trueUtc + targetOffset);
|
||||
const formatted = target.toISOString().replace('T', ' ').slice(0, 19);
|
||||
|
||||
return {
|
||||
formatted,
|
||||
targetOffset: offsetLabel(targetOffset),
|
||||
sourceOffset: offsetLabel(sourceOffset),
|
||||
};
|
||||
}
|
||||
|
||||
/** Full IANA list on modern runtimes; a small fallback elsewhere. */
|
||||
export function listTimezones(): string[] {
|
||||
const intl = Intl as unknown as { supportedValuesOf?: (key: string) => string[] };
|
||||
if (typeof intl.supportedValuesOf === 'function') {
|
||||
const zones = intl.supportedValuesOf('timeZone');
|
||||
return zones.includes('UTC') ? zones : ['UTC', ...zones];
|
||||
}
|
||||
return ['UTC', 'Europe/London', 'Europe/Paris', 'Europe/Berlin', 'America/New_York', 'America/Los_Angeles', 'Asia/Shanghai', 'Asia/Tokyo', 'Australia/Sydney'];
|
||||
}
|
||||
69
src/tools/timezone-converter/timezone-converter.vue
Normal file
69
src/tools/timezone-converter/timezone-converter.vue
Normal file
|
|
@ -0,0 +1,69 @@
|
|||
<script setup lang="ts">
|
||||
import { convertBetweenTimezones, listTimezones } from './timezone-converter.models';
|
||||
import TextareaCopyable from '@/components/TextareaCopyable.vue';
|
||||
import { useValidation } from '@/composable/validation';
|
||||
import { withDefaultOnError } from '@/utils/defaults';
|
||||
|
||||
const zones = listTimezones();
|
||||
const zoneOptions = zones.map(zone => ({ label: zone, value: zone }));
|
||||
|
||||
const dateTime = useStorage('timezone-converter:dateTime', '2024-01-15 12:00:00');
|
||||
const fromTz = useStorage('timezone-converter:from', 'UTC');
|
||||
const toTz = useStorage('timezone-converter:to', 'Asia/Shanghai');
|
||||
|
||||
const result = computed(() =>
|
||||
withDefaultOnError(() => convertBetweenTimezones({ dateTime: dateTime.value, fromTz: fromTz.value, toTz: toTz.value }), undefined),
|
||||
);
|
||||
|
||||
const validation = useValidation({
|
||||
source: dateTime,
|
||||
rules: [
|
||||
{
|
||||
validator: value => !value || !Number.isNaN(Date.parse(`${value.trim().replace(' ', 'T')}Z`)),
|
||||
message: 'Invalid date/time (expected YYYY-MM-DD HH:mm:ss)',
|
||||
},
|
||||
],
|
||||
});
|
||||
|
||||
function setNow() {
|
||||
const now = new Date();
|
||||
const pad = (n: number) => String(n).padStart(2, '0');
|
||||
dateTime.value = `${now.getFullYear()}-${pad(now.getMonth() + 1)}-${pad(now.getDate())} ${pad(now.getHours())}:${pad(now.getMinutes())}:${pad(now.getSeconds())}`;
|
||||
}
|
||||
</script>
|
||||
|
||||
<template>
|
||||
<div style="max-width: 640px; margin: 0 auto;">
|
||||
<c-card mb-3>
|
||||
<c-input-text
|
||||
v-model:value="dateTime"
|
||||
label="Date and time (interpreted in the source timezone)"
|
||||
placeholder="YYYY-MM-DD HH:mm:ss"
|
||||
:validation="validation"
|
||||
mb-2
|
||||
/>
|
||||
|
||||
<n-grid cols="2" x-gap="12" mb-2>
|
||||
<n-gi>
|
||||
<c-select v-model:value="fromTz" label="From timezone" label-position="top" :options="zoneOptions" filterable />
|
||||
</n-gi>
|
||||
<n-gi>
|
||||
<c-select v-model:value="toTz" label="To timezone" label-position="top" :options="zoneOptions" filterable />
|
||||
</n-gi>
|
||||
</n-grid>
|
||||
|
||||
<c-button @click="setNow()">
|
||||
Use current time
|
||||
</c-button>
|
||||
</c-card>
|
||||
|
||||
<c-card v-if="result">
|
||||
<n-form-item label="Time in target timezone:" label-placement="top">
|
||||
<TextareaCopyable :value="`${result.formatted} (${result.targetOffset})`" copy-placement="outside" mb-1 />
|
||||
</n-form-item>
|
||||
<div text-xs op-60>
|
||||
Source zone offset: {{ result.sourceOffset }} · DST rules applied automatically
|
||||
</div>
|
||||
</c-card>
|
||||
</div>
|
||||
</template>
|
||||
Loading…
Add table
Add a link
Reference in a new issue