diff --git a/.github/release.yml b/.github/release.yml new file mode 100644 index 00000000..8973334c --- /dev/null +++ b/.github/release.yml @@ -0,0 +1,4 @@ +changelog: + exclude: + labels: + - 'i18n' diff --git a/.github/workflows/i18n-automerge.yml b/.github/workflows/i18n-automerge.yml index 155869e4..f3beff2b 100644 --- a/.github/workflows/i18n-automerge.yml +++ b/.github/workflows/i18n-automerge.yml @@ -3,6 +3,8 @@ name: i18n PR auto-merge on: pull_request: types: [opened, synchronize, reopened, labeled] + branches: + - main jobs: run-and-merge: @@ -10,14 +12,21 @@ jobs: github.event.pull_request.base.ref == 'main' && github.event.pull_request.head.ref == 'l10n_main' runs-on: ubuntu-latest - # concurrency: - # group: ${{ github.workflow }}-${{ github.ref }} - # cancel-in-progress: true steps: - uses: actions/checkout@v4 with: fetch-depth: 0 + - run: sleep 15 + + - name: Check if the branch is dirty + run: | + git fetch origin ${{ github.event.pull_request.head.ref }} + if [ $(git rev-parse HEAD) != $(git rev-parse origin/${{ github.event.pull_request.head.ref }}) ]; then + echo "Branch is dirty. Exiting..." + exit 0 + fi + - name: Check auto-merge conditions run: | BASE_SHA="${{ github.event.pull_request.base.sha }}" @@ -47,8 +56,16 @@ jobs: exit 0 else echo "More than 50 lines have been changed. Merging pull request." + + # List of locales changed + LOCALES_CHANGED=$(git diff --name-only $BASE_SHA $HEAD_SHA | grep '\.po$' | awk -F '/' '{print $NF}' | sed 's/\.po$//' | tr '\n' ',' | sed 's/,$//') + + # Better subject + # "i18n updates ([LOCALES_CHANGED])" + SUBJECT="i18n updates ($LOCALES_CHANGED)" + PR_NUMBER=$(echo ${{ github.event.pull_request.number }}) - gh pr merge $PR_NUMBER --auto --squash || true + gh pr merge $PR_NUMBER --author "github-actions[bot]@users.noreply.github.com" --squash --subject "$SUBJECT" || true fi env: GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} diff --git a/.github/workflows/i18n-update-readme.yml b/.github/workflows/i18n-update-readme.yml new file mode 100644 index 00000000..78b50c69 --- /dev/null +++ b/.github/workflows/i18n-update-readme.yml @@ -0,0 +1,34 @@ +name: Update README with list of i18n volunteers + +on: + schedule: + # Every week + - cron: '0 0 * * 0' + workflow_dispatch: + +jobs: + update-readme: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - uses: actions/setup-node@v4 + with: + node-version: 20 + - run: npm ci + - run: | + npm run fetch-i18n-volunteers + npm run readme:i18n-volunteers + + # Commit & push if there are changes + if git diff --quiet README.md; then + echo "No changes to README.md" + else + echo "Changes to README.md" + git config --global user.email "github-actions[bot]@users.noreply.github.com" + git config --global user.name "github-actions[bot]" + git add README.md + git commit -m "Update README.md" + git push + fi + env: + CROWDIN_ACCESS_TOKEN: ${{ secrets.CROWDIN_ACCESS_TOKEN }} diff --git a/.github/workflows/update-catalogs.yml b/.github/workflows/update-catalogs.yml index 55291fea..5a635c65 100644 --- a/.github/workflows/update-catalogs.yml +++ b/.github/workflows/update-catalogs.yml @@ -1,17 +1,18 @@ -name: Update catalogs +name: Update Catalogs on: - pull_request_target: - types: - - closed + push: + branches: + - l10n_main workflow_dispatch: jobs: update-catalogs: - if: github.event.pull_request.merged == true runs-on: ubuntu-latest steps: - uses: actions/checkout@v4 + with: + ref: l10n_main - uses: actions/setup-node@v4 with: node-version: 20 @@ -27,5 +28,5 @@ jobs: git config --global user.name "github-actions[bot]" git add src/data/catalogs.json git commit -m "Update catalogs.json" - git push origin HEAD:main + git push origin HEAD:l10n_main || true fi diff --git a/README.md b/README.md index 7cbf9988..18529e68 100644 --- a/README.md +++ b/README.md @@ -292,6 +292,59 @@ Costs involved in running and developing this web app: [![Contributors](https://contrib.rocks/image?repo=cheeaun/phanpy)](https://github.com/cheeaun/phanpy/graphs/contributors) +### Translation volunteers + + +- alidsds11 (Arabic) +- BoFFire (Arabic, French, Kabyle) +- Brawaru (Russian) +- cbasje (Dutch) +- cbo92 (French) +- CDN (Chinese Simplified) +- dannypsnl (Chinese Traditional) +- databio (Catalan) +- drydenwu (Chinese Traditional) +- elissarc (French) +- ElPamplina (Spanish) +- Fitik (Esperanto, Hebrew) +- Freeesia (Japanese) +- ghose (Galician) +- hongminhee (Korean) +- isard (Catalan) +- karlafej (Czech) +- katullo11 (Italian) +- Kytta (German) +- llun (Thai) +- lucasofchirst (Occitan, Portuguese, Portuguese, Brazilian) +- marcin.kozinski (Polish) +- mojosoeun (Korean) +- moreal (Korean) +- MrWillCom (Chinese Simplified) +- nclm (French) +- pazpi (Italian) +- punkrockgirl (Basque) +- radecos (French) +- Razem (Czech) +- realpixelcode (German) +- rezahosseinzadeh (Persian) +- rwmpelstilzchen (Esperanto, Hebrew) +- SadmL (Russian) +- Sky_NiniKo (French) +- Su5hicz (Czech) +- Talos00 (Italian) +- tferrermo (Spanish) +- tux93 (German) +- Urbestro (Esperanto, Spanish) +- UsualUsername (Russian) +- Vac31. (Lithuanian) +- valtlai (Finnish) +- xabi_itzultzaile (Basque) +- xen4n (Ukrainian) +- xqueralt (Catalan) +- ZiriSut (Kabyle) +- zkreml (Czech) + + ## Backstory I am one of the earliest users of Twitter. Twitter was launched on [15 July 2006](https://en.wikipedia.org/wiki/Twitter). I joined on December 2006 and my [first tweet](https://twitter.com/cheeaun/status/1298723) was posted on 18 December 2006. diff --git a/i18n-volunteers.json b/i18n-volunteers.json new file mode 100644 index 00000000..4c9aa6a5 --- /dev/null +++ b/i18n-volunteers.json @@ -0,0 +1,345 @@ +[ + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12571163/medium/9f3ea938f4243f5ffe2a43f814ddc9e8_default.png", + "username": "alidsds11", + "languages": [ + "Arabic" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13170041/medium/603136896af17fc005fd592ce3f48717_default.png", + "username": "BoFFire", + "languages": [ + "Arabic", + "French", + "Kabyle" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12898464/medium/d3758a76b894bade4bf271c9b32ea69b.png", + "username": "Brawaru", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15460040/medium/1cfcfe5f5511b783b5d9f2b968bad819.png", + "username": "cbasje", + "languages": [ + "Dutch" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15525631/medium/51293156034d0236f1a1020c10f7d539_default.png", + "username": "cbo92", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15910131/medium/67fab7eeab5551853450e76e2ef19e59.jpeg", + "username": "CDN", + "languages": [ + "Chinese Simplified" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16556801/medium/ed5e501ca1f3cc6525d2da28db646346.jpeg", + "username": "dannypsnl", + "languages": [ + "Chinese Traditional" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/3711/medium/d95ddd44e8dcb3a039f8a3463aed781d_default.png", + "username": "databio", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12618120/medium/ccb11bd042bbf4c7189033f7af2dbd32_default.png", + "username": "drydenwu", + "languages": [ + "Chinese Traditional" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13557465/medium/8feebf3677fa80c01e8c54c4fbe097e0_default.png", + "username": "elissarc", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16528627/medium/9036f6eced0257f4e1ea4c5bd499de2d_default.png", + "username": "ElPamplina", + "languages": [ + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14277386/medium/29b30d2c73a214000e3941c9978f49e4_default.png", + "username": "Fitik", + "languages": [ + "Esperanto", + "Hebrew" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14444512/medium/99d0e7a3076deccbdfe0aa0b0612308c.jpeg", + "username": "Freeesia", + "languages": [ + "Japanese" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12617257/medium/a201650da44fed28890b0e0d8477a663.jpg", + "username": "ghose", + "languages": [ + "Galician" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15248754/medium/0dac6334ea0f4e8d4194a605c0a5594a.jpeg", + "username": "hongminhee", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13454728/medium/1f78b7124b3c962bc4ae55e8d701fc91_default.png", + "username": "isard", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532403/medium/4cefb19623bcc44d7cdb9e25aebf5250.jpeg", + "username": "karlafej", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15791971/medium/88bdda3090339f16f6083390d32bb434_default.png", + "username": "katullo11", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14677260/medium/e53420d200961f48602324e18c091bdc.png", + "username": "Kytta", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16529521/medium/ae6add93a901b0fefa2d9b1077920d73.png", + "username": "llun", + "languages": [ + "Thai" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16291756/medium/e1c4210f15537394cc764b8bc2dffe37.jpg", + "username": "lucasofchirst", + "languages": [ + "Occitan", + "Portuguese", + "Portuguese, Brazilian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16537713/medium/825f0bf1a14fc545a76891a52839d86e_default.png", + "username": "marcin.kozinski", + "languages": [ + "Polish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/12882812/medium/77744d8db46e9a3e09030e1a02b7a572.jpeg", + "username": "mojosoeun", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13613969/medium/c7834ddc0ada84a79671697a944bb274.png", + "username": "moreal", + "languages": [ + "Korean" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14158861/medium/ba1ff31dc5743b067ea6685f735229a5_default.png", + "username": "MrWillCom", + "languages": [ + "Chinese Simplified" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15652333/medium/7f36f289f9e2fe41d89ad534a1047f0e.png", + "username": "nclm", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16539461/medium/2f41b9f0b802c1d200a6ab62167a7229_default.png", + "username": "pazpi", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15106977/medium/54bf93b19af8bbfdee579ea51685bafa.jpeg", + "username": "punkrockgirl", + "languages": [ + "Basque" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16536247/medium/f010c8e718a36229733a8b58f6bad2a4_default.png", + "username": "radecos", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16538917/medium/092ec03f56f9dd1cbce94379fa4d4d38.png", + "username": "Razem", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14345134/medium/89a299239890c79a1d791d08ec3951dc.png", + "username": "realpixelcode", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16527325/medium/37ebb27e7a50f7f85ae93beafc7028a2.jpg", + "username": "rezahosseinzadeh", + "languages": [ + "Persian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/13422319/medium/66632a98d73d48e36753d94ebcec9d4f.png", + "username": "rwmpelstilzchen", + "languages": [ + "Esperanto", + "Hebrew" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16538605/medium/bcdb6e3286b7d6237923f3a9383eed29.png", + "username": "SadmL", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14565190/medium/79100599131b7776e9803e4b696915a3_default.png", + "username": "Sky_NiniKo", + "languages": [ + "French" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532441/medium/1a47e8d80c95636e02d2260f6e233ca5.png", + "username": "Su5hicz", + "languages": [ + "Czech" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16533843/medium/7314c15492ef90118c33a80a427e6c87_default.png", + "username": "Talos00", + "languages": [ + "Italian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16530049/medium/683f3581620c6b4a5c753b416ed695a7.jpeg", + "username": "tferrermo", + "languages": [ + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16527851/medium/649e5a9a8a8cc61ced670d89e9cca082.png", + "username": "tux93", + "languages": [ + "German" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16529833/medium/2991a65722acd721849656223014cd49.png", + "username": "Urbestro", + "languages": [ + "Esperanto", + "Spanish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16539171/medium/db6fb87481026c72b895adfb94e17d2c_default.png", + "username": "UsualUsername", + "languages": [ + "Russian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14427566/medium/ab733b5044c21867fc5a9d1b22cd2c03.png", + "username": "Vac31.", + "languages": [ + "Lithuanian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16026914/medium/e3ca187f354a298ef0c9d02a0ed17be7.jpg", + "username": "valtlai", + "languages": [ + "Finnish" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/15982109/medium/9c03062bdc1d3c6d384dbfead97c26ba.jpeg", + "username": "xabi_itzultzaile", + "languages": [ + "Basque" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16556017/medium/216e0f7a0c35b079920366939a3aaca7_default.png", + "username": "xen4n", + "languages": [ + "Ukrainian" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16532657/medium/f309f319266e1ff95f3070eab0c9a9d9_default.png", + "username": "xqueralt", + "languages": [ + "Catalan" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/14041603/medium/6ab77a0467b06aeb49927c6d9c409f89.jpg", + "username": "ZiriSut", + "languages": [ + "Kabyle" + ] + }, + { + "avatarUrl": "https://crowdin-static.downloads.crowdin.com/avatar/16530601/medium/e1b6d5c24953b6405405c1ab33c0fa46.jpeg", + "username": "zkreml", + "languages": [ + "Czech" + ] + } +] \ No newline at end of file diff --git a/lingui.config.js b/lingui.config.js index 14841e5d..e612248d 100644 --- a/lingui.config.js +++ b/lingui.config.js @@ -1,7 +1,8 @@ -import { LOCALES } from './src/locales'; +import { ALL_LOCALES } from './src/locales'; const config = { - locales: LOCALES, + locales: ALL_LOCALES, + sourceLocale: 'en', pseudoLocale: 'pseudo-LOCALE', fallbackLocales: { default: 'en', diff --git a/package-lock.json b/package-lock.json index 93fb2726..1a18e531 100644 --- a/package-lock.json +++ b/package-lock.json @@ -14,9 +14,9 @@ "@github/text-expander-element": "~2.7.1", "@iconify-icons/mingcute": "~1.2.9", "@justinribeiro/lite-youtube": "~1.5.0", - "@lingui/detect-locale": "~4.11.3", - "@lingui/macro": "~4.11.3", - "@lingui/react": "~4.11.3", + "@lingui/detect-locale": "~4.11.4", + "@lingui/macro": "~4.11.4", + "@lingui/react": "~4.11.4", "@szhsin/react-menu": "~4.2.2", "compare-versions": "~6.1.1", "fast-blurhash": "~1.1.4", @@ -25,6 +25,7 @@ "html-prettify": "~1.0.7", "idb-keyval": "~6.2.1", "intl-locale-textinfo-polyfill": "~2.1.1", + "js-cookie": "~3.0.5", "just-debounce-it": "~3.2.0", "lz-string": "~1.5.0", "masto": "~6.8.0", @@ -33,7 +34,7 @@ "p-throttle": "~6.2.0", "preact": "~10.23.2", "punycode": "~2.3.1", - "react-hotkeys-hook": "~4.5.0", + "react-hotkeys-hook": "~4.5.1", "react-intersection-observer": "~9.13.0", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", @@ -42,25 +43,25 @@ "tinyld": "~1.3.4", "toastify-js": "~1.12.0", "uid": "~2.0.2", - "use-debounce": "~10.0.2", + "use-debounce": "~10.0.3", "use-long-press": "~3.2.0", "use-resize-observer": "~9.1.0", - "valtio": "1.13.2" + "valtio": "2.0.0" }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "~4.3.1", - "@lingui/cli": "~4.11.3", - "@lingui/vite-plugin": "~4.11.3", + "@lingui/cli": "~4.11.4", + "@lingui/vite-plugin": "~4.11.4", "@preact/preset-vite": "~2.9.0", "babel-plugin-macros": "~3.1.0", - "postcss": "~8.4.41", + "postcss": "~8.4.45", "postcss-dark-theme-class": "~1.3.0", - "postcss-preset-env": "~10.0.1", + "postcss-preset-env": "~10.0.2", "twitter-text": "~3.1.0", - "vite": "~5.4.1", + "vite": "~5.4.3", "vite-plugin-generate-file": "~0.2.0", - "vite-plugin-html-config": "~1.0.11", - "vite-plugin-pwa": "~0.20.1", + "vite-plugin-html-config": "~2.0.2", + "vite-plugin-pwa": "~0.20.3", "vite-plugin-remove-console": "~2.2.0", "vite-plugin-run": "~0.5.2", "workbox-cacheable-response": "~7.1.0", @@ -1866,9 +1867,9 @@ } }, "node_modules/@csstools/cascade-layer-name-parser": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.0.tgz", - "integrity": "sha512-9GEQIvTMrjXfYaVnw1+FteDX5yF65CZq4ttYP75O3CANQevaCJ9jVVTiZt9YTpjYIk8C1mmf52y2S4Hr/CaE/g==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/cascade-layer-name-parser/-/cascade-layer-name-parser-2.0.1.tgz", + "integrity": "sha512-G9ZYN5+yr/E6xYSiy1BwOEFP5p88ZtWo8sL4NztKBkRRAwRkzVGa70M+D+fYHugMID5jkLeNt5X9jYd5EaVuyg==", "dev": true, "funding": [ { @@ -1884,8 +1885,8 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" } }, "node_modules/@csstools/color-helpers": { @@ -1908,9 +1909,9 @@ } }, "node_modules/@csstools/css-calc": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.0.0.tgz", - "integrity": "sha512-fxPxNrEVGeej4F35Xt69Q7gPMKa7oEGNxeP1DpA01sWpTF3Yhgux/0slVX3jLHd7dhlszeQlNAUhpAorVxoHdQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-calc/-/css-calc-2.0.1.tgz", + "integrity": "sha512-e59V+sNp6e5m+9WnTUydA1DQO70WuKUdseflRpWmXxocF/h5wWGIxUjxfvLtajcmwstH0vm6l0reKMzcyI757Q==", "dev": true, "funding": [ { @@ -1926,14 +1927,14 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" } }, "node_modules/@csstools/css-color-parser": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.1.tgz", - "integrity": "sha512-++7I+Z7S/BWedPlR4z8aW1zsvtJFufFbpdPwdx5+W50dq5EYLV3sulitSNMry0BNmNMzeczdQij/f4C+ch01vQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/css-color-parser/-/css-color-parser-3.0.2.tgz", + "integrity": "sha512-mNg7A6HnNjlm0we/pDS9dUafOuBxcanN0TBhEGeIk6zZincuk0+mAbnBqfVs29NlvWHZ8diwTG6g5FeU8246sA==", "dev": true, "funding": [ { @@ -1947,20 +1948,20 @@ ], "dependencies": { "@csstools/color-helpers": "^5.0.1", - "@csstools/css-calc": "^2.0.0" + "@csstools/css-calc": "^2.0.1" }, "engines": { "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" } }, "node_modules/@csstools/css-parser-algorithms": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.0.tgz", - "integrity": "sha512-20hEErXV9GEx15qRbsJVzB91ryayx1F2duHPBrfZXQAHz/dJG0u/611URpr28+sFjm3EI7U17Pj9SVA9NSAGJA==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-parser-algorithms/-/css-parser-algorithms-3.0.1.tgz", + "integrity": "sha512-lSquqZCHxDfuTg/Sk2hiS0mcSFCEBuj49JfzPHJogDBT0mGCyY5A1AQzBWngitrp7i1/HAZpIgzF/VjhOEIJIg==", "dev": true, "funding": [ { @@ -1976,13 +1977,13 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-tokenizer": "^3.0.1" } }, "node_modules/@csstools/css-tokenizer": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.0.tgz", - "integrity": "sha512-efZvfJyYrqH9hPCKtOBywlTsCXnEzAI9sLHFzUsDpBb+1bQ+bxJnwL9V2bRKv9w4cpIp75yxGeZRaVKoMQnsEg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/css-tokenizer/-/css-tokenizer-3.0.1.tgz", + "integrity": "sha512-UBqaiu7kU0lfvaP982/o3khfXccVlHPWp0/vwwiIgDF0GmqqqxoiXC/6FCjlS9u92f7CoEz6nXKQnrn1kIAkOw==", "dev": true, "funding": [ { @@ -1999,9 +2000,9 @@ } }, "node_modules/@csstools/media-query-list-parser": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.0.tgz", - "integrity": "sha512-W0JlkUFwXjo703wt06AcaWuUcS+6x6IEDyxV6W65Sw+vLCYp+uPsrps+PXTiIfN0V1Pqj5snPzN7EYLmbz1zjg==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/media-query-list-parser/-/media-query-list-parser-3.0.1.tgz", + "integrity": "sha512-HNo8gGD02kHmcbX6PvCoUuOQvn4szyB9ca63vZHKX5A81QytgDG4oxG4IaEfHTlEZSZ6MjPEMWIVU+zF2PZcgw==", "dev": true, "funding": [ { @@ -2017,8 +2018,8 @@ "node": ">=18" }, "peerDependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" } }, "node_modules/@csstools/postcss-cascade-layers": { @@ -2048,9 +2049,9 @@ } }, "node_modules/@csstools/postcss-color-function": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.1.tgz", - "integrity": "sha512-nRnwVdqdMUIsE7cGbI+La4fxME6tT9bVDRYfBHW/0QTLwCVJN4+DC/3kqiU6AdTne31hUBGPwcH1uzkuc4fO5A==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-function/-/postcss-color-function-4.0.2.tgz", + "integrity": "sha512-q/W3RXh66SM7WqxW3/KU6koL8nOgqyB/wrcU3+ThXnNtXY2+k8UgdE301ISJpMt6PDyYgC7eMaIBo535RvFIgw==", "dev": true, "funding": [ { @@ -2063,9 +2064,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2077,9 +2078,9 @@ } }, "node_modules/@csstools/postcss-color-mix-function": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.1.tgz", - "integrity": "sha512-RRdu3CppF9dTn3AvDkeEkOL8ZDpDh/TF6YLV1JKl768BQk0XJ026xWfttoL911k0g8yprES3wFujjLsK0XhsEg==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-color-mix-function/-/postcss-color-mix-function-3.0.2.tgz", + "integrity": "sha512-zG9PHNzZVCRk6eprm+T/ybrnuiwLdO+RR7+GCtNut+NZJGtPJj6bfPOEX23aOlMslLcRAlN6QOpxH3tovn+WpA==", "dev": true, "funding": [ { @@ -2092,9 +2093,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2106,9 +2107,9 @@ } }, "node_modules/@csstools/postcss-content-alt-text": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.0.tgz", - "integrity": "sha512-1pPjMaSUftwn/4N7RtJif91cB6BBEo0LQX2vryrDMF5uKDqt4RMpIi9ZFTsKtcXBFZexNGEWXZzPABnooJGkzQ==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-content-alt-text/-/postcss-content-alt-text-2.0.1.tgz", + "integrity": "sha512-TWjjewVZqdkjavsi8a2THuXgkhUum1k/m4QJpZpzOv72q6WnaoQZGSj5t5uCs7ymJr0H3qj6JcXMwMApSWUOGQ==", "dev": true, "funding": [ { @@ -2121,8 +2122,8 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2134,9 +2135,9 @@ } }, "node_modules/@csstools/postcss-exponential-functions": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.0.tgz", - "integrity": "sha512-sH7MBlsn6yft6xQ8uQ9MCWFHbZCUL3HIN3IntUabv75syl0dPldECTqLJix5q5ilSQxDQ1L+LajeZk84S6GG9w==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-exponential-functions/-/postcss-exponential-functions-2.0.1.tgz", + "integrity": "sha512-A/MG8es3ylFzZ30oYIQUyJcMOfTfCs0dqqBMzeuzaPRlx4q/72WG+BbKe/pL9BUNIWsM0Q8jn3e3la8enjHJJA==", "dev": true, "funding": [ { @@ -2149,9 +2150,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-calc": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" }, "engines": { "node": ">=18" @@ -2187,9 +2188,9 @@ } }, "node_modules/@csstools/postcss-gamut-mapping": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.1.tgz", - "integrity": "sha512-bCQ609PZsGUmrTVeGaPgYF27DFQ7gg2no3j6qXY3MOAVjfPRvMIlGdpLejhgYra1VUoTNA1SUqHLNgFWoJ/pRA==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gamut-mapping/-/postcss-gamut-mapping-2.0.2.tgz", + "integrity": "sha512-/1ur3ca9RWg/KnbLlxaDswyjLSGoaHNDruAzrVhkn5axgd7LOH6JHCBRhrKDafdMw9bf4MQrYFoaLfHAPekLFg==", "dev": true, "funding": [ { @@ -2202,9 +2203,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" }, "engines": { "node": ">=18" @@ -2214,9 +2215,9 @@ } }, "node_modules/@csstools/postcss-gradients-interpolation-method": { - "version": "5.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.1.tgz", - "integrity": "sha512-ZINUsXxFrJ5bpfpq772BQzu5K23dDFoQwvyeQRHHQpDOS8hMIoBMmjDjtZV5fGJ/gtL/blKUvytAyrgBzaqvUQ==", + "version": "5.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-gradients-interpolation-method/-/postcss-gradients-interpolation-method-5.0.2.tgz", + "integrity": "sha512-qRpvA4sduAfiV9yZG4OM7q/h2Qhr3lg+GrHe9NZwuzWnfSDLGh+Dh4Ea6fQ+1++jdKXW/Cb4/vHRp0ssQYra4w==", "dev": true, "funding": [ { @@ -2229,9 +2230,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2243,9 +2244,9 @@ } }, "node_modules/@csstools/postcss-hwb-function": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.1.tgz", - "integrity": "sha512-1SluTV2F2WiWPw5CHQ/UOsUrO5y89VDQlOICzHIF3Mx50YdTf0qYZ/dTXL/Fa+1AgzSn4IYt51XXjgxI7pe/jw==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-hwb-function/-/postcss-hwb-function-4.0.2.tgz", + "integrity": "sha512-RUBVCyJE1hTsf9vGp3zrALeMollkAlHRFKm+T36y67nLfOOf+6GNQsdTGFAyLrY65skcm8ddC26Jp1n9ZIauEA==", "dev": true, "funding": [ { @@ -2258,9 +2259,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2347,9 +2348,9 @@ } }, "node_modules/@csstools/postcss-light-dark-function": { - "version": "2.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.1.tgz", - "integrity": "sha512-RHliBdalIg7KZNwv5B1VwF1qFEhmz3ZIbZXyxOH1g7W72S0oazMKIvYVgHenCxHCxWOxSR0ipZ+8kHa+fm4O5A==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-light-dark-function/-/postcss-light-dark-function-2.0.2.tgz", + "integrity": "sha512-QAWWDJtJ7ywzhaMe09QwhjhuwB0XN04fW1MFwoEJMcYyiQub4a57mVFV+ngQEekUhsqe/EtKVCzyOx4q3xshag==", "dev": true, "funding": [ { @@ -2362,8 +2363,8 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2466,9 +2467,9 @@ } }, "node_modules/@csstools/postcss-logical-viewport-units": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.0.tgz", - "integrity": "sha512-7a0d7TLfHP3k7n+XGj5NJopgyKgl/VKyAPapYIo97aujB7+8M4dBE1Og0OmWng+H/drQWXoSlCI3pov5XwVtxQ==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-logical-viewport-units/-/postcss-logical-viewport-units-3.0.1.tgz", + "integrity": "sha512-JsfaoTiBqIuRE+CYL4ZpYKOqJ965GyiMH4b8UrY0Z7i5GfMiHZrK7xtTB29piuyKQzrW+Z8w3PAExhwND9cuAQ==", "dev": true, "funding": [ { @@ -2481,7 +2482,7 @@ } ], "dependencies": { - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/utilities": "^2.0.0" }, "engines": { @@ -2492,9 +2493,9 @@ } }, "node_modules/@csstools/postcss-media-minmax": { - "version": "2.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.0.tgz", - "integrity": "sha512-21Cmy5QWbexbpKAAJntGomjn644BWWs7gXkx/Vid1SjqxIRmPUB/dcJ4xBWwjpFuhrPKzT8a3Pr+IJv9R9v9Yg==", + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-minmax/-/postcss-media-minmax-2.0.1.tgz", + "integrity": "sha512-EMa3IgUip+F/MwH4r2KfIA9ym9hQkT2PpR9MOukdomfGGCFuw9V3n/iIOBKziN1qfeddsYoOvtYOKQcHU2yIjg==", "dev": true, "funding": [ { @@ -2507,10 +2508,10 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", - "@csstools/media-query-list-parser": "^3.0.0" + "@csstools/css-calc": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1" }, "engines": { "node": ">=18" @@ -2520,9 +2521,9 @@ } }, "node_modules/@csstools/postcss-media-queries-aspect-ratio-number-values": { - "version": "3.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.0.tgz", - "integrity": "sha512-TV8Q7ec0zbCxlmTmUF8CvAWWbK3q9ops3+sGCc6rHAGrfkoA+HyMGwJBZudddZQOV9MZS949mhtYIV4AgIRizw==", + "version": "3.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-media-queries-aspect-ratio-number-values/-/postcss-media-queries-aspect-ratio-number-values-3.0.1.tgz", + "integrity": "sha512-JTzMQz//INahTALkvXnC5lC2fJKzwb5PY443T2zaM9hAzM7nzHMLIlEfFgdtBahVIBtBSalMefdxNr99LGW1lQ==", "dev": true, "funding": [ { @@ -2535,9 +2536,9 @@ } ], "dependencies": { - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", - "@csstools/media-query-list-parser": "^3.0.0" + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1" }, "engines": { "node": ">=18" @@ -2598,9 +2599,9 @@ } }, "node_modules/@csstools/postcss-oklab-function": { - "version": "4.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.1.tgz", - "integrity": "sha512-hEJ83YhqNII3/TBGcJLjSkNx65p4Zbz6YFm6ww2BRRO223/GTFOHT2ElicWmnBtoZWKORgysI4wtLv3p6LZSFQ==", + "version": "4.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-oklab-function/-/postcss-oklab-function-4.0.2.tgz", + "integrity": "sha512-2iSK/T77PHMeorakBAk/WLxSodfIJ/lmi6nxEkuruXfhGH7fByZim4Fw6ZJf4B73SVieRSH2ep8zvYkA2ZfRtA==", "dev": true, "funding": [ { @@ -2613,9 +2614,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2652,9 +2653,9 @@ } }, "node_modules/@csstools/postcss-relative-color-syntax": { - "version": "3.0.1", - "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.1.tgz", - "integrity": "sha512-GbcQPmfBOjKomHuOVB6troujQg65ykCMt4OGot75Bdev7jAHC9hd0AX5qMprpG9AF0dA012curAVqY0ehmdYwQ==", + "version": "3.0.2", + "resolved": "https://registry.npmjs.org/@csstools/postcss-relative-color-syntax/-/postcss-relative-color-syntax-3.0.2.tgz", + "integrity": "sha512-aBpuUdpJBswNGfw6lOkhown2cZ0YXrMjASye56nkoRpgRe9yDF4BM1fvEuakrCDiaeoUzVaI4SF6+344BflXfQ==", "dev": true, "funding": [ { @@ -2667,9 +2668,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -2706,9 +2707,9 @@ } }, "node_modules/@csstools/postcss-stepped-value-functions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.0.tgz", - "integrity": "sha512-sJUW1axQuxRyD59zr9hMJ6MoM/99UkxNc7fxJ1kFdTl1B5dS3TxvVzY1fRq1C/JsgBw6uNzfy/i52SrVNtbbXw==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-stepped-value-functions/-/postcss-stepped-value-functions-4.0.1.tgz", + "integrity": "sha512-dk3KqVcIEYzy9Mvx8amoBbk123BWgd5DfjXDiPrEqxGma37PG7m/MoMmHQhuVHIjvPDHoJwyIZi2yy7j0RA5fw==", "dev": true, "funding": [ { @@ -2721,9 +2722,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-calc": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" }, "engines": { "node": ">=18" @@ -2759,9 +2760,9 @@ } }, "node_modules/@csstools/postcss-trigonometric-functions": { - "version": "4.0.0", - "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.0.tgz", - "integrity": "sha512-M7CivX++ZOQvnF+eZ8FHg2X8GYOfSUFH6GRtr7mGeIgd38WmT1WCBogqBvz/Y5x9VUeor9EuJX2K06bP7p4BuA==", + "version": "4.0.1", + "resolved": "https://registry.npmjs.org/@csstools/postcss-trigonometric-functions/-/postcss-trigonometric-functions-4.0.1.tgz", + "integrity": "sha512-QHOYuN3bzS/rcpAygFhJxJUtD8GuJEWF6f9Zm518Tq/cSMlcTgU+v0geyi5EqbmYxKMig2oKCKUSGqOj9gehkg==", "dev": true, "funding": [ { @@ -2774,9 +2775,9 @@ } ], "dependencies": { - "@csstools/css-calc": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0" + "@csstools/css-calc": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1" }, "engines": { "node": ">=18" @@ -3537,18 +3538,18 @@ "license": "MIT" }, "node_modules/@lingui/babel-plugin-extract-messages": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-4.11.3.tgz", - "integrity": "sha512-wLiquhtxE7qUmoKl4UStFI1XgrCkk9zwxc8z62LPpbutkyxO21B5k8fBUGlgWoKJaXbpvS8VIU8j2663q99JnQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/babel-plugin-extract-messages/-/babel-plugin-extract-messages-4.11.4.tgz", + "integrity": "sha512-7gUOsYJ4wIjv/0tGxAGiGpgWKCybFPP0tCQMz6baa9xcsk8Vp7Xmuf9og1AD6EMawjStibQsQyE6xaRnJgpoHg==", "dev": true, "engines": { "node": ">=16.0.0" } }, "node_modules/@lingui/cli": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-4.11.3.tgz", - "integrity": "sha512-ykJLmQciK81I0Cd/iLg8dSpESV9Hnsbw5+G98IEAf4exvoUGRJ2UzkeNc/HeGx3D5Fg+TI8YNWwCbZ7NAOsDCQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/cli/-/cli-4.11.4.tgz", + "integrity": "sha512-PauBkvi++YkYAYq6w9MwkBmE6KiDE9wRh5DkN8yFPRcfj64vkE2l1HFENCqL/jg63kr8esOAiueD9+CtUGVyDg==", "dev": true, "dependencies": { "@babel/core": "^7.21.0", @@ -3556,11 +3557,11 @@ "@babel/parser": "^7.21.2", "@babel/runtime": "^7.21.0", "@babel/types": "^7.21.2", - "@lingui/babel-plugin-extract-messages": "4.11.3", - "@lingui/conf": "4.11.3", - "@lingui/core": "4.11.3", - "@lingui/format-po": "4.11.3", - "@lingui/message-utils": "4.11.3", + "@lingui/babel-plugin-extract-messages": "4.11.4", + "@lingui/conf": "4.11.4", + "@lingui/core": "4.11.4", + "@lingui/format-po": "4.11.4", + "@lingui/message-utils": "4.11.4", "babel-plugin-macros": "^3.0.1", "chalk": "^4.1.0", "chokidar": "3.5.1", @@ -3571,7 +3572,7 @@ "esbuild": "^0.17.10", "glob": "^7.1.4", "inquirer": "^7.3.3", - "micromatch": "4.0.2", + "micromatch": "^4.0.2", "normalize-path": "^3.0.0", "ora": "^5.1.0", "pathe": "^1.1.0", @@ -4069,9 +4070,9 @@ } }, "node_modules/@lingui/conf": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-4.11.3.tgz", - "integrity": "sha512-KwUJDrbzlZEXmlmqttpB/Sd9hiIo0sqccsZaYTHzW/uULZT9T11aw/f6RcPLCVJeSKazg/7dJhR1cKlxKzvjKA==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/conf/-/conf-4.11.4.tgz", + "integrity": "sha512-FC12yP0MHzu2QN5/4JkFHdz25l4Yu2ucjj3K12Y8tW/75oPh+n8k2u1+3/M68zWoqf5yyFvU4m2A+gxEmeR0iw==", "dependencies": { "@babel/runtime": "^7.20.13", "chalk": "^4.1.0", @@ -4149,12 +4150,12 @@ } }, "node_modules/@lingui/core": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/core/-/core-4.11.3.tgz", - "integrity": "sha512-IjJxn0Kvzv+ICnGlMqn8wRIQLikCJVrolb1oyi6GqtbiuPiwKYeU0D6Hbe146lMaTN8juc3tOCBS+Fr02XqFIQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/core/-/core-4.11.4.tgz", + "integrity": "sha512-W0bBIFe44s//Qs+RQ+NMfzK5vAm9oEKyDddlN94Db6rzeUT/IJo7N+T75A6Bya8v/BrtF2G/W4b77eS3sd0utw==", "dependencies": { "@babel/runtime": "^7.20.13", - "@lingui/message-utils": "4.11.3", + "@lingui/message-utils": "4.11.4", "unraw": "^3.0.0" }, "engines": { @@ -4162,21 +4163,21 @@ } }, "node_modules/@lingui/detect-locale": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-4.11.3.tgz", - "integrity": "sha512-5QJsNOzRcuT97gkgMk/yUqt52adXdd+yzs/29yleWpFEANO/Z9Zt/ozwdpThf8zeFsi8TM5GRZFQ1ScpKxuPOQ==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/detect-locale/-/detect-locale-4.11.4.tgz", + "integrity": "sha512-JmIuFSyB8KitO02iAWV3+v0brkHYx72akiwhe2Jta9gzRVPeDFj2uyzO+UJXzzhPylAkX9o3suXIMXpy1dmXAQ==", "engines": { "node": ">=16.0.0" } }, "node_modules/@lingui/format-po": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-4.11.3.tgz", - "integrity": "sha512-RgEkoo0aEAk7X1xGrApcpqkz6GLdzkRLGw2jo3mmCVR0P7P9sWbJL/cd01GmR+HzAOo8Zx5oIayaKh9iyJS8tA==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/format-po/-/format-po-4.11.4.tgz", + "integrity": "sha512-PiWbTiiNgYZTFVuBHYirtAG98cDxrT0IwmSvETQk4YbaqCdn28/J7fRFZScsKqe8LmpnxX4EXZLs/R0MP2jLHA==", "dev": true, "dependencies": { - "@lingui/conf": "4.11.3", - "@lingui/message-utils": "4.11.3", + "@lingui/conf": "4.11.4", + "@lingui/message-utils": "4.11.4", "date-fns": "^3.6.0", "pofile": "^1.1.4" }, @@ -4185,15 +4186,15 @@ } }, "node_modules/@lingui/macro": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-4.11.3.tgz", - "integrity": "sha512-D0me8ZRtH0ylSavhKZu0FYf5mJ1y6kDMMPjYVDyiT5ooOI/5jjv9LIAqrdYGCBygnwsxOG1dzDw6+3s5GTs+Bg==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/macro/-/macro-4.11.4.tgz", + "integrity": "sha512-mgfyBpp/UCiaJxr+DTBtaCUKnq2fV9JrmUmBumC9PaFDCXYfjB0A2gaq2XEgn9PmUKuzC7PGs1sPJ3TBJ8uGTw==", "dependencies": { "@babel/runtime": "^7.20.13", "@babel/types": "^7.20.7", - "@lingui/conf": "4.11.3", - "@lingui/core": "4.11.3", - "@lingui/message-utils": "4.11.3" + "@lingui/conf": "4.11.4", + "@lingui/core": "4.11.4", + "@lingui/message-utils": "4.11.4" }, "engines": { "node": ">=16.0.0" @@ -4204,9 +4205,9 @@ } }, "node_modules/@lingui/message-utils": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-4.11.3.tgz", - "integrity": "sha512-ZSw3OoKbknOw3nSrqt194g2F8r0guKow9csb46zlL7zX/yOWCaj767wvSvMoglZtVvurfQs4NPv2cohYlWORNw==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/message-utils/-/message-utils-4.11.4.tgz", + "integrity": "sha512-ZTCDhGbj5EN+P9Ajcj0Gq9uDP3HZTRW6/kT09WkiFgL4NayYLksPvgBk29sIglsS6M+Y6Iw2BrUK403SZjZKgw==", "dependencies": { "@messageformat/parser": "^5.0.0", "js-sha256": "^0.10.1" @@ -4216,12 +4217,12 @@ } }, "node_modules/@lingui/react": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/react/-/react-4.11.3.tgz", - "integrity": "sha512-FuorwDsz5zDpUNpyj7J8ZKqJrrVxOz1EtQ3aJGJsmnTtVO01N3nR3ckMzpYvZ71XXdDEvhUC9ihmiKbFvpaZ/w==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/react/-/react-4.11.4.tgz", + "integrity": "sha512-f7re4HhjI6CLBV1CY/PcI3VYP5zS4rtfU33speWnfkymsxGIXQv4ol3BqrgPLGhypMl2nKcL5nfL+LewrLIW8g==", "dependencies": { "@babel/runtime": "^7.20.13", - "@lingui/core": "4.11.3" + "@lingui/core": "4.11.4" }, "engines": { "node": ">=16.0.0" @@ -4231,13 +4232,13 @@ } }, "node_modules/@lingui/vite-plugin": { - "version": "4.11.3", - "resolved": "https://registry.npmjs.org/@lingui/vite-plugin/-/vite-plugin-4.11.3.tgz", - "integrity": "sha512-CNPtcXN/pdM8jXKLZFwazCczK7DagwcLvYL8WRt6m0wxpaMcR2s15/Sp/S6gL0PN8OXHykSzcg9nBMgXfgMaHw==", + "version": "4.11.4", + "resolved": "https://registry.npmjs.org/@lingui/vite-plugin/-/vite-plugin-4.11.4.tgz", + "integrity": "sha512-491PbqPyeb3E5+vDyOlMJsNZZIvhkH7A5F8RgaBk3lKSU5w7k1aI5nTqhOcYpZNEzT3tIWNZCUQ3u5znoP3cEQ==", "dev": true, "dependencies": { - "@lingui/cli": "4.11.3", - "@lingui/conf": "4.11.3" + "@lingui/cli": "4.11.4", + "@lingui/conf": "4.11.4" }, "engines": { "node": ">=16.0.0" @@ -5869,14 +5870,6 @@ "url": "https://github.com/sponsors/ljharb" } }, - "node_modules/derive-valtio": { - "version": "0.1.0", - "resolved": "https://registry.npmjs.org/derive-valtio/-/derive-valtio-0.1.0.tgz", - "integrity": "sha512-OCg2UsLbXK7GmmpzMXhYkdO64vhJ1ROUUGaTFyHjVwEdMEcTTRj7W1TxLbSBxdY8QLBPCcp66MTyaSy0RpO17A==", - "peerDependencies": { - "valtio": "*" - } - }, "node_modules/dom-input-range": { "version": "1.1.6", "resolved": "https://registry.npmjs.org/dom-input-range/-/dom-input-range-1.1.6.tgz", @@ -7472,6 +7465,14 @@ "jiti": "bin/jiti.js" } }, + "node_modules/js-cookie": { + "version": "3.0.5", + "resolved": "https://registry.npmjs.org/js-cookie/-/js-cookie-3.0.5.tgz", + "integrity": "sha512-cEiJEAEoIbWfCZYKWhVwFuvPX1gETRYPw6LlaTKoxD3s2AkXzkCjnp6h0V77ozyqj0jakteJ4YqDJT830+lVGw==", + "engines": { + "node": ">=14" + } + }, "node_modules/js-sha256": { "version": "0.10.1", "resolved": "https://registry.npmjs.org/js-sha256/-/js-sha256-0.10.1.tgz", @@ -8364,9 +8365,9 @@ } }, "node_modules/postcss": { - "version": "8.4.41", - "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.41.tgz", - "integrity": "sha512-TesUflQ0WKZqAvg52PWL6kHgLKP6xB6heTOdoYM0Wt2UHyxNa4K25EZZMgKns3BH1RLVbZCREPpLY0rhnNoHVQ==", + "version": "8.4.45", + "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.45.tgz", + "integrity": "sha512-7KTLTdzdZZYscUc65XmjFiB73vBhBfbPztCYdUNvlaso9PrzjzcmjqBPR0lNGkcVlcO4BjiO5rK/qNz+XAen1Q==", "dev": true, "funding": [ { @@ -8433,9 +8434,9 @@ } }, "node_modules/postcss-color-functional-notation": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.1.tgz", - "integrity": "sha512-8/wf01pTH3XHT37wre+E2GNcsttZ62PWSJ0DE66GO+Uzk+uyr9DH+V3cdJG+BqezCD/T5lBC1s5/t7Y12ps8QQ==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-color-functional-notation/-/postcss-color-functional-notation-7.0.2.tgz", + "integrity": "sha512-c2WkR0MS73s+P5SgY1KBaSEE61Rj+miW095rkWDnMQxbTCQkp6y/jft8U0QMxEsI4k1Pd4PdV+TP9/1zIDR6XQ==", "dev": true, "funding": [ { @@ -8448,9 +8449,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -8514,9 +8515,9 @@ } }, "node_modules/postcss-custom-media": { - "version": "11.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.0.tgz", - "integrity": "sha512-tZ4qTYSOqH7YFi8psEQB2v2zBRbbJex9FgPef2Qss8DlWxnYpBNHquvMmVBR8uIt6hW0+prDsg7UJDp6XLIf8w==", + "version": "11.0.1", + "resolved": "https://registry.npmjs.org/postcss-custom-media/-/postcss-custom-media-11.0.1.tgz", + "integrity": "sha512-vfBliYVgEEJUFXCRPQ7jYt1wlD322u+/5GT0tZqMVYFInkpDHfjhU3nk2quTRW4uFc/umOOqLlxvrEOZRvloMw==", "dev": true, "funding": [ { @@ -8529,10 +8530,10 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", - "@csstools/media-query-list-parser": "^3.0.0" + "@csstools/cascade-layer-name-parser": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", + "@csstools/media-query-list-parser": "^3.0.1" }, "engines": { "node": ">=18" @@ -8542,9 +8543,9 @@ } }, "node_modules/postcss-custom-properties": { - "version": "14.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.0.tgz", - "integrity": "sha512-GD/suWYQAplXJujsyOswYP+oX9xs29eBNwGloPj4Ub+3/Rq1Set+ZeGmHJfN2Y2+x9vUxAX4eeNJFmtk6VBv4A==", + "version": "14.0.1", + "resolved": "https://registry.npmjs.org/postcss-custom-properties/-/postcss-custom-properties-14.0.1.tgz", + "integrity": "sha512-SB4GjuZjIq5GQFNbxFrirQPbkdbJooyNy8bh+fcJ8ZG0oasJTflTTtR4geb56h+FBVDIb9Hx4v/NiG2caOj8nQ==", "dev": true, "funding": [ { @@ -8557,9 +8558,9 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/cascade-layer-name-parser": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/utilities": "^2.0.0", "postcss-value-parser": "^4.2.0" }, @@ -8571,9 +8572,9 @@ } }, "node_modules/postcss-custom-selectors": { - "version": "8.0.0", - "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.0.tgz", - "integrity": "sha512-nW6RWjH+jaWvXEgm/AzMhtVjMXcKmrTWsM/eJn/ujnJI5uEOPTxvl3eCFFCFKC2DiZcOP5HLH5EeX0DIemFzBQ==", + "version": "8.0.1", + "resolved": "https://registry.npmjs.org/postcss-custom-selectors/-/postcss-custom-selectors-8.0.1.tgz", + "integrity": "sha512-2McIpyhAeKhUzVqrP4ZyMBpK5FuD+Y9tpQwhcof49652s7gez8057cSaOg/epYcKlztSYxb0GHfi7W5h3JoGUg==", "dev": true, "funding": [ { @@ -8586,9 +8587,9 @@ } ], "dependencies": { - "@csstools/cascade-layer-name-parser": "^2.0.0", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/cascade-layer-name-parser": "^2.0.1", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "postcss-selector-parser": "^6.1.0" }, "engines": { @@ -8784,9 +8785,9 @@ } }, "node_modules/postcss-lab-function": { - "version": "7.0.1", - "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.1.tgz", - "integrity": "sha512-G9ecsdU+TtdHJvaTrfbIyOa3iHHJMZXdvsLXJSN8IP4cfg3XraozacAt6P7xzaILIC3XPGMM149oKhf2tjPubg==", + "version": "7.0.2", + "resolved": "https://registry.npmjs.org/postcss-lab-function/-/postcss-lab-function-7.0.2.tgz", + "integrity": "sha512-h4ARGLIBtC1PmCHsLgTWWj8j1i1CXoaht4A5RlITDX2z9AeFBak0YlY6sdF4oJGljrep+Dg2SSccIj4QnFbRDg==", "dev": true, "funding": [ { @@ -8799,9 +8800,9 @@ } ], "dependencies": { - "@csstools/css-color-parser": "^3.0.1", - "@csstools/css-parser-algorithms": "^3.0.0", - "@csstools/css-tokenizer": "^3.0.0", + "@csstools/css-color-parser": "^3.0.2", + "@csstools/css-parser-algorithms": "^3.0.1", + "@csstools/css-tokenizer": "^3.0.1", "@csstools/postcss-progressive-custom-properties": "^4.0.0", "@csstools/utilities": "^2.0.0" }, @@ -8948,9 +8949,9 @@ } }, "node_modules/postcss-preset-env": { - "version": "10.0.1", - "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.1.tgz", - "integrity": "sha512-I8m4pBJWcUmPLu1z8HySEiAROKIMA0nwUYCGr1gJOGFP9BdwPWPBSYvA2KGH0VjUZ8AAAvwwXXWnxHTWq3f7UQ==", + "version": "10.0.2", + "resolved": "https://registry.npmjs.org/postcss-preset-env/-/postcss-preset-env-10.0.2.tgz", + "integrity": "sha512-PMxqnz0RQYMUmUi6p4P7BhC9EVGyEUCIdwn4vJ7Fy1jvc2QP4mMH75BSBB1mBFqjl3x4xYwyCNMhGZ8y0+/qOA==", "dev": true, "funding": [ { @@ -8964,34 +8965,34 @@ ], "dependencies": { "@csstools/postcss-cascade-layers": "^5.0.0", - "@csstools/postcss-color-function": "^4.0.1", - "@csstools/postcss-color-mix-function": "^3.0.1", - "@csstools/postcss-content-alt-text": "^2.0.0", - "@csstools/postcss-exponential-functions": "^2.0.0", + "@csstools/postcss-color-function": "^4.0.2", + "@csstools/postcss-color-mix-function": "^3.0.2", + "@csstools/postcss-content-alt-text": "^2.0.1", + "@csstools/postcss-exponential-functions": "^2.0.1", "@csstools/postcss-font-format-keywords": "^4.0.0", - "@csstools/postcss-gamut-mapping": "^2.0.1", - "@csstools/postcss-gradients-interpolation-method": "^5.0.1", - "@csstools/postcss-hwb-function": "^4.0.1", + "@csstools/postcss-gamut-mapping": "^2.0.2", + "@csstools/postcss-gradients-interpolation-method": "^5.0.2", + "@csstools/postcss-hwb-function": "^4.0.2", "@csstools/postcss-ic-unit": "^4.0.0", "@csstools/postcss-initial": "^2.0.0", "@csstools/postcss-is-pseudo-class": "^5.0.0", - "@csstools/postcss-light-dark-function": "^2.0.1", + "@csstools/postcss-light-dark-function": "^2.0.2", "@csstools/postcss-logical-float-and-clear": "^3.0.0", "@csstools/postcss-logical-overflow": "^2.0.0", "@csstools/postcss-logical-overscroll-behavior": "^2.0.0", "@csstools/postcss-logical-resize": "^3.0.0", - "@csstools/postcss-logical-viewport-units": "^3.0.0", - "@csstools/postcss-media-minmax": "^2.0.0", - "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.0", + "@csstools/postcss-logical-viewport-units": "^3.0.1", + "@csstools/postcss-media-minmax": "^2.0.1", + "@csstools/postcss-media-queries-aspect-ratio-number-values": "^3.0.1", "@csstools/postcss-nested-calc": "^4.0.0", "@csstools/postcss-normalize-display-values": "^4.0.0", - "@csstools/postcss-oklab-function": "^4.0.1", + "@csstools/postcss-oklab-function": "^4.0.2", "@csstools/postcss-progressive-custom-properties": "^4.0.0", - "@csstools/postcss-relative-color-syntax": "^3.0.1", + "@csstools/postcss-relative-color-syntax": "^3.0.2", "@csstools/postcss-scope-pseudo-class": "^4.0.0", - "@csstools/postcss-stepped-value-functions": "^4.0.0", + "@csstools/postcss-stepped-value-functions": "^4.0.1", "@csstools/postcss-text-decoration-shorthand": "^4.0.1", - "@csstools/postcss-trigonometric-functions": "^4.0.0", + "@csstools/postcss-trigonometric-functions": "^4.0.1", "@csstools/postcss-unset-value": "^4.0.0", "autoprefixer": "^10.4.19", "browserslist": "^4.23.1", @@ -9001,12 +9002,12 @@ "cssdb": "^8.1.0", "postcss-attribute-case-insensitive": "^7.0.0", "postcss-clamp": "^4.1.0", - "postcss-color-functional-notation": "^7.0.1", + "postcss-color-functional-notation": "^7.0.2", "postcss-color-hex-alpha": "^10.0.0", "postcss-color-rebeccapurple": "^10.0.0", - "postcss-custom-media": "^11.0.0", - "postcss-custom-properties": "^14.0.0", - "postcss-custom-selectors": "^8.0.0", + "postcss-custom-media": "^11.0.1", + "postcss-custom-properties": "^14.0.1", + "postcss-custom-selectors": "^8.0.1", "postcss-dir-pseudo-class": "^9.0.0", "postcss-double-position-gradients": "^6.0.0", "postcss-focus-visible": "^10.0.0", @@ -9014,7 +9015,7 @@ "postcss-font-variant": "^5.0.0", "postcss-gap-properties": "^6.0.0", "postcss-image-set-function": "^7.0.0", - "postcss-lab-function": "^7.0.1", + "postcss-lab-function": "^7.0.2", "postcss-logical": "^8.0.0", "postcss-nesting": "^13.0.0", "postcss-opacity-percentage": "^2.0.0", @@ -9192,9 +9193,9 @@ } }, "node_modules/proxy-compare": { - "version": "2.6.0", - "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-2.6.0.tgz", - "integrity": "sha512-8xuCeM3l8yqdmbPoYeLbrAXCBWu19XEYc5/F28f5qOaoAIMyfmBUkl5axiK+x9olUvRlcekvnm98AP9RDngOIw==" + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/proxy-compare/-/proxy-compare-3.0.0.tgz", + "integrity": "sha512-y44MCkgtZUCT9tZGuE278fB7PWVf7fRYy0vbRXAts2o5F0EfC4fIQrvQQGBJo1WJbFcVLXzApOscyJuZqHQc1w==" }, "node_modules/pseudolocale": { "version": "2.1.0", @@ -9271,9 +9272,9 @@ } }, "node_modules/react-hotkeys-hook": { - "version": "4.5.0", - "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.0.tgz", - "integrity": "sha512-Samb85GSgAWFQNvVt3PS90LPPGSf9mkH/r4au81ZP1yOIFayLC3QAvqTgGtJ8YEDMXtPmaVBs6NgipHO6h4Mug==", + "version": "4.5.1", + "resolved": "https://registry.npmjs.org/react-hotkeys-hook/-/react-hotkeys-hook-4.5.1.tgz", + "integrity": "sha512-scAEJOh3Irm0g95NIn6+tQVf/OICCjsQsC9NBHfQws/Vxw4sfq1tDQut5fhTEvPraXhu/sHxRd9lOtxzyYuNAg==", "peerDependencies": { "react": ">=16.8.1", "react-dom": ">=16.8.1" @@ -10477,14 +10478,14 @@ } }, "node_modules/use-debounce": { - "version": "10.0.2", - "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.2.tgz", - "integrity": "sha512-MwBiJK2dk+2qhMDVDCPRPeLuIekKfH2t1UYMnrW9pwcJJGFDbTLliSMBz2UKGmE1PJs8l3XoMqbIU1MemMAJ8g==", + "version": "10.0.3", + "resolved": "https://registry.npmjs.org/use-debounce/-/use-debounce-10.0.3.tgz", + "integrity": "sha512-DxQSI9ZKso689WM1mjgGU3ozcxU1TJElBJ3X6S4SMzMNcm2lVH0AHmyXB+K7ewjz2BSUKJTDqTcwtSMRfB89dg==", "engines": { "node": ">= 16.0.0" }, "peerDependencies": { - "react": ">=16.8.0" + "react": "*" } }, "node_modules/use-long-press": { @@ -10509,15 +10510,6 @@ "react-dom": "16.8.0 - 18" } }, - "node_modules/use-sync-external-store": { - "version": "1.2.0", - "resolved": "https://registry.npmjs.org/use-sync-external-store/-/use-sync-external-store-1.2.0.tgz", - "integrity": "sha512-eEgnFxGQ1Ife9bzYs6VLi8/4X6CObHMw9Qr9tPY43iKwsPw8xE8+EFsf/2cFZ5S3esXgpWgtSCtLNS41F+sKPA==", - "license": "MIT", - "peerDependencies": { - "react": "^16.8.0 || ^17.0.0 || ^18.0.0" - } - }, "node_modules/util-deprecate": { "version": "1.0.2", "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", @@ -10525,20 +10517,18 @@ "dev": true }, "node_modules/valtio": { - "version": "1.13.2", - "resolved": "https://registry.npmjs.org/valtio/-/valtio-1.13.2.tgz", - "integrity": "sha512-Qik0o+DSy741TmkqmRfjq+0xpZBXi/Y6+fXZLn0xNF1z/waFMbE3rkivv5Zcf9RrMUp6zswf2J7sbh2KBlba5A==", + "version": "2.0.0", + "resolved": "https://registry.npmjs.org/valtio/-/valtio-2.0.0.tgz", + "integrity": "sha512-SzUU5UUK/vBRfHWXihwkJE55YNj8zhOkzxPOexcz0xIIT6Oux5VLynCmzyME2bYuEWcktW2NTaaLbpUydEsHiw==", "dependencies": { - "derive-valtio": "0.1.0", - "proxy-compare": "2.6.0", - "use-sync-external-store": "1.2.0" + "proxy-compare": "^3.0.0" }, "engines": { "node": ">=12.20.0" }, "peerDependencies": { - "@types/react": ">=16.8", - "react": ">=16.8" + "@types/react": ">=18.0.0", + "react": ">=18.0.0" }, "peerDependenciesMeta": { "@types/react": { @@ -10550,14 +10540,14 @@ } }, "node_modules/vite": { - "version": "5.4.1", - "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.1.tgz", - "integrity": "sha512-1oE6yuNXssjrZdblI9AfBbHCC41nnyoVoEZxQnID6yvQZAFBzxxkqoFLtHUMkYunL8hwOLEjgTuxpkRxvba3kA==", + "version": "5.4.3", + "resolved": "https://registry.npmjs.org/vite/-/vite-5.4.3.tgz", + "integrity": "sha512-IH+nl64eq9lJjFqU+/yrRnrHPVTlgy42/+IzbOdaFDVlyLgI/wDlf+FCobXLX1cT0X5+7LMyH1mIy2xJdLfo8Q==", "dev": true, "dependencies": { "esbuild": "^0.21.3", - "postcss": "^8.4.41", - "rollup": "^4.13.0" + "postcss": "^8.4.43", + "rollup": "^4.20.0" }, "bin": { "vite": "bin/vite.js" @@ -10621,22 +10611,21 @@ } }, "node_modules/vite-plugin-html-config": { - "version": "1.0.11", - "resolved": "https://registry.npmjs.org/vite-plugin-html-config/-/vite-plugin-html-config-1.0.11.tgz", - "integrity": "sha512-hUybhgI+/LQQ5q6xoMMsTvI4PBuQD/Wv6Z1vtDPVWjanS8weCIexXuLLYNGD/93f0v8W2hpNfXpmxgpZMahJ0g==", + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/vite-plugin-html-config/-/vite-plugin-html-config-2.0.2.tgz", + "integrity": "sha512-g09u0XsmgKyMUIp1RZSyNSkJWvIusaXxw3KylyxU3vkCq7/G8hyemLctT+4IvO42fCPlNySmrNC9g0qSoKmvpw==", "dev": true, - "license": "MIT", "engines": { "node": ">=12.0.0" }, "peerDependencies": { - "vite": ">=2.0.0" + "vite": ">=5.0.0" } }, "node_modules/vite-plugin-pwa": { - "version": "0.20.1", - "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.1.tgz", - "integrity": "sha512-M6Pk4b18i5ryrhKgiIF8Zud0HGphYiCbEfLsCdlvmwn/CEnS6noVwfIDG/+3V7r6yxpPV/gLiKw+rIlCCiCCoQ==", + "version": "0.20.3", + "resolved": "https://registry.npmjs.org/vite-plugin-pwa/-/vite-plugin-pwa-0.20.3.tgz", + "integrity": "sha512-aqCOWWSwfX4o6H+6NyEvhzFs3eENBqYFKUK4FYx5OZ3jGio73BE189bPz9+BBgjHBLozldQVSmZTHySVC2zNkg==", "dev": true, "dependencies": { "debug": "^4.3.4", diff --git a/package.json b/package.json index 1fc4f75b..0160af88 100644 --- a/package.json +++ b/package.json @@ -11,7 +11,9 @@ "bundle-visualizer": "npx vite-bundle-visualizer", "messages:extract": "lingui extract", "messages:extract:clean": "lingui extract --locale en --clean", - "messages:compile": "lingui compile" + "messages:compile": "lingui compile", + "fetch-i18n-volunteers": "env $(cat .env.local | grep -v \"#\" | xargs) node scripts/fetch-i18n-volunteers.js", + "readme:i18n-volunteers": "node scripts/update-i18n-volunteers-readme.js" }, "dependencies": { "@formatjs/intl-localematcher": "~0.5.4", @@ -20,9 +22,9 @@ "@github/text-expander-element": "~2.7.1", "@iconify-icons/mingcute": "~1.2.9", "@justinribeiro/lite-youtube": "~1.5.0", - "@lingui/detect-locale": "~4.11.3", - "@lingui/macro": "~4.11.3", - "@lingui/react": "~4.11.3", + "@lingui/detect-locale": "~4.11.4", + "@lingui/macro": "~4.11.4", + "@lingui/react": "~4.11.4", "@szhsin/react-menu": "~4.2.2", "compare-versions": "~6.1.1", "fast-blurhash": "~1.1.4", @@ -31,6 +33,7 @@ "html-prettify": "~1.0.7", "idb-keyval": "~6.2.1", "intl-locale-textinfo-polyfill": "~2.1.1", + "js-cookie": "~3.0.5", "just-debounce-it": "~3.2.0", "lz-string": "~1.5.0", "masto": "~6.8.0", @@ -39,7 +42,7 @@ "p-throttle": "~6.2.0", "preact": "~10.23.2", "punycode": "~2.3.1", - "react-hotkeys-hook": "~4.5.0", + "react-hotkeys-hook": "~4.5.1", "react-intersection-observer": "~9.13.0", "react-quick-pinch-zoom": "~5.1.0", "react-router-dom": "6.6.2", @@ -48,25 +51,25 @@ "tinyld": "~1.3.4", "toastify-js": "~1.12.0", "uid": "~2.0.2", - "use-debounce": "~10.0.2", + "use-debounce": "~10.0.3", "use-long-press": "~3.2.0", "use-resize-observer": "~9.1.0", - "valtio": "1.13.2" + "valtio": "2.0.0" }, "devDependencies": { "@ianvs/prettier-plugin-sort-imports": "~4.3.1", - "@lingui/cli": "~4.11.3", - "@lingui/vite-plugin": "~4.11.3", + "@lingui/cli": "~4.11.4", + "@lingui/vite-plugin": "~4.11.4", "@preact/preset-vite": "~2.9.0", "babel-plugin-macros": "~3.1.0", - "postcss": "~8.4.41", + "postcss": "~8.4.45", "postcss-dark-theme-class": "~1.3.0", - "postcss-preset-env": "~10.0.1", + "postcss-preset-env": "~10.0.2", "twitter-text": "~3.1.0", - "vite": "~5.4.1", + "vite": "~5.4.3", "vite-plugin-generate-file": "~0.2.0", - "vite-plugin-html-config": "~1.0.11", - "vite-plugin-pwa": "~0.20.1", + "vite-plugin-html-config": "~2.0.2", + "vite-plugin-pwa": "~0.20.3", "vite-plugin-remove-console": "~2.2.0", "vite-plugin-run": "~0.5.2", "workbox-cacheable-response": "~7.1.0", diff --git a/scripts/catalogs.js b/scripts/catalogs.js index 39e19450..debb0d43 100644 --- a/scripts/catalogs.js +++ b/scripts/catalogs.js @@ -73,18 +73,21 @@ function IDN(inputCode, outputCode) { return result; } -// Sort by percentage -const sortedCatalogs = Object.entries(catalogs) - .sort((a, b) => b[1] - a[1]) +const fullCatalogs = Object.entries(catalogs) + // sort by key + .sort((a, b) => a[0].localeCompare(b[0])) .map(([code, completion]) => { const nativeName = IDN(code, code); const name = IDN('en', code); - // let names = {}; return { code, nativeName, name, completion }; }); +// Sort by completion +const sortedCatalogs = [...fullCatalogs].sort( + (a, b) => b.completion - a.completion, +); console.table(sortedCatalogs); const path = 'src/data/catalogs.json'; -fs.writeFileSync(path, JSON.stringify(sortedCatalogs, null, 2)); +fs.writeFileSync(path, JSON.stringify(fullCatalogs, null, 2)); console.log('File written:', path); diff --git a/scripts/fetch-i18n-volunteers.js b/scripts/fetch-i18n-volunteers.js new file mode 100644 index 00000000..b0e4132e --- /dev/null +++ b/scripts/fetch-i18n-volunteers.js @@ -0,0 +1,131 @@ +import fs from 'fs'; + +const { CROWDIN_ACCESS_TOKEN } = process.env; + +const PROJECT_ID = '703337'; + +if (!CROWDIN_ACCESS_TOKEN) { + throw new Error('CROWDIN_ACCESS_TOKEN is not set'); +} + +// Generate Report + +let REPORT_ID = null; +{ + const response = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + method: 'POST', + body: JSON.stringify({ + name: 'top-members', + schema: { + format: 'json', + }, + }), + }, + ); + const json = await response.json(); + console.log(`Report ID: ${json?.data?.identifier}`); + REPORT_ID = json?.data?.identifier; +} + +if (!REPORT_ID) { + throw new Error('Report ID is not found'); +} + +// Check Report Generation Status +let finished = false; +{ + let maxPolls = 10; + do { + maxPolls--; + if (maxPolls < 0) break; + + // Wait for 1 second + await new Promise((resolve) => setTimeout(resolve, 1000)); + + const status = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports/${REPORT_ID}`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + }, + ); + const json = await status.json(); + const progress = json?.data?.progress; + console.log(`Progress: ${progress}% (${maxPolls} retries left)`); + finished = json?.data?.status === 'finished'; + } while (!finished); +} + +if (!finished) { + throw new Error('Failed to generate report'); +} + +// Download Report +let reportURL = null; +{ + const response = await fetch( + `https://api.crowdin.com/api/v2/projects/${PROJECT_ID}/reports/${REPORT_ID}/download`, + { + headers: { + Authorization: `Bearer ${CROWDIN_ACCESS_TOKEN}`, + 'Content-Type': 'application/json', + }, + }, + ); + const json = await response.json(); + reportURL = json?.data?.url; + console.log(`Report URL: ${reportURL}`); +} + +if (!reportURL) { + throw new Error('Report URL is not found'); +} + +// Actually download the report +let members = null; +{ + const response = await fetch(reportURL); + const json = await response.json(); + + const { data } = json; + + if (!data?.length) { + throw new Error('No data found'); + } + + // Sort by 'user.fullName' + data.sort((a, b) => a.user.username.localeCompare(b.user.username)); + members = data + .filter((item) => { + const isMyself = item.user.username === 'cheeaun'; + const translatedMoreThanZero = item.translated > 0; + + return !isMyself && translatedMoreThanZero; + }) + .map((item) => ({ + avatarUrl: item.user.avatarUrl, + username: item.user.username, + languages: item.languages.map((lang) => lang.name), + })); + + console.log(members); + + if (members?.length) { + fs.writeFileSync( + 'i18n-volunteers.json', + JSON.stringify(members, null, '\t'), + ); + } +} + +if (!members?.length) { + throw new Error('No members found'); +} diff --git a/scripts/update-i18n-volunteers-readme.js b/scripts/update-i18n-volunteers-readme.js new file mode 100644 index 00000000..571c8677 --- /dev/null +++ b/scripts/update-i18n-volunteers-readme.js @@ -0,0 +1,27 @@ +// Find for and inject list of i18n volunteers in between + +import fs from 'fs'; + +const i18nVolunteers = JSON.parse(fs.readFileSync('i18n-volunteers.json')); + +const readme = fs.readFileSync('README.md', 'utf8'); + +const i18nVolunteersStart = ''; +const i18nVolunteersEnd = ''; + +const i18nVolunteersList = i18nVolunteers + .map((member) => { + return `- ${ + member.username + } (${member.languages.join(', ')})`; + }) + .join('\n'); + +const readmeUpdated = readme.replace( + new RegExp(`${i18nVolunteersStart}.*${i18nVolunteersEnd}`, 's'), + `${i18nVolunteersStart}\n${i18nVolunteersList}\n${i18nVolunteersEnd}`, +); + +fs.writeFileSync('README.md', readmeUpdated); + +console.log('Updated README.md'); diff --git a/src/app.css b/src/app.css index 857d7fbe..7d13d40e 100644 --- a/src/app.css +++ b/src/app.css @@ -1810,16 +1810,17 @@ body > .szh-menu-container { env(safe-area-inset-bottom) env(safe-area-inset-left); } .szh-menu { - padding: 8px 0; + padding: 4px 0; margin: 0; font-size: var(--text-size); background-color: var(--bg-color); - border: 1px solid var(--outline-color); + border: 1px solid var(--outline-stronger-color); border-radius: 8px; - box-shadow: 0 3px 16px -3px var(--drop-shadow-color); + box-shadow: 0 3px 8px var(--drop-shadow-color), + 0 6px 32px -6px var(--drop-shadow-color); text-align: start; /* animation: appear-smooth 0.15s ease-in-out; */ - width: 16em; + min-width: 16em; max-width: 90vw; /* overflow: hidden; */ } @@ -1874,13 +1875,16 @@ body > .szh-menu-container { display: flex; gap: 8px; align-items: center; - line-height: 1.1; + line-height: 1.3; padding: 8px 16px !important; /* transition: all 0.1s ease-in-out; */ text-decoration: none; white-space: nowrap; overflow: hidden; text-overflow: ellipsis; + + --menu-item-bg-inset: 0 4px; + --menu-item-bg-color: var(--button-bg-color); } .szh-menu .szh-menu__item--focusable { background-color: transparent; @@ -1917,9 +1921,30 @@ body > .szh-menu-container { .szh-menu__item:not(.szh-menu__item--disabled, .szh-menu__item--hover) { color: var(--text-color); } +.szh-menu .szh-menu__item:not(.menu-field) { + position: relative; + & > * { + /* z-index: 1; */ + } + + &:before { + content: ''; + background-color: var(--menu-item-bg-color); + position: absolute; + inset: var(--menu-item-bg-inset); + border-radius: 4px; + z-index: -1; + opacity: 0; + } +} .szh-menu .szh-menu__item--hover:not(.menu-field) { color: var(--button-text-color); - background-color: var(--button-bg-color); + /* background-color: var(--button-bg-color); */ + background-color: transparent; + + &:before { + opacity: 1; + } } .szh-menu__divider { background-color: var(--divider-color); @@ -1995,10 +2020,12 @@ body > .szh-menu-container { } .szh-menu .szh-menu__item.danger:not(.szh-menu__item--disabled).szh-menu__item--hover { - background-color: var(--red-text-color); + /* background-color: var(--red-text-color); */ + --menu-item-bg-color: var(--red-text-color); @media (prefers-color-scheme: dark) { - background-color: var(--red-color); + /* background-color: var(--red-color); */ + --menu-item-bg-color: var(--red-color); } } .szh-menu @@ -2038,12 +2065,20 @@ body > .szh-menu-container { ); } } + + &:before { + content: ''; + } + } + + .szh-menu__item--hover { + background-color: var(--menu-item-bg-color); } } .menu-control-group-horizontal:first-child, li[role='none'] + .menu-control-group-horizontal { - margin-top: -8px; + margin-top: -4px; margin-bottom: -4px; .szh-menu__item { @@ -2078,6 +2113,8 @@ body > .szh-menu-container { } .szh-menu .menu-wrap { + min-width: 16em; + width: min-content; display: flex; flex-wrap: wrap; } @@ -2092,11 +2129,10 @@ body > .szh-menu-container { background-color: var(--bg-blur-color); backdrop-filter: blur(8px) saturate(3); border: var(--hairline-width) solid var(--bg-color); - box-shadow: 0 3px 8px -1px var(--drop-shadow-color); text-shadow: 0 var(--hairline-width) var(--bg-color), 0 0 8px var(--bg-color); } .glass-menu .szh-menu__item--hover { - background-color: var(--button-bg-blur-color); + /* background-color: var(--button-bg-blur-color); */ text-shadow: none; } diff --git a/src/app.jsx b/src/app.jsx index d3f1a45e..4db0a8cb 100644 --- a/src/app.jsx +++ b/src/app.jsx @@ -56,7 +56,11 @@ import { getAccessToken } from './utils/auth'; import focusDeck from './utils/focus-deck'; import states, { initStates, statusKey } from './utils/states'; import store from './utils/store'; -import { getCurrentAccount, setCurrentAccountID } from './utils/store-utils'; +import { + getAccount, + getCurrentAccount, + setCurrentAccountID, +} from './utils/store-utils'; import './utils/toast-alert'; @@ -317,9 +321,10 @@ function App() { window.location.pathname || '/', ); - const clientID = store.session.get('clientID'); - const clientSecret = store.session.get('clientSecret'); - const vapidKey = store.session.get('vapidKey'); + const clientID = store.sessionCookie.get('clientID'); + const clientSecret = store.sessionCookie.get('clientSecret'); + const vapidKey = store.sessionCookie.get('vapidKey'); + const verifier = store.sessionCookie.get('codeVerifier'); (async () => { setUIState('loading'); @@ -328,22 +333,46 @@ function App() { client_id: clientID, client_secret: clientSecret, code, + code_verifier: verifier || undefined, }); - const client = initClient({ instance: instanceURL, accessToken }); - await Promise.allSettled([ - initPreferences(client), - initInstance(client, instanceURL), - initAccount(client, instanceURL, accessToken, vapidKey), - ]); - initStates(); + if (accessToken) { + const client = initClient({ instance: instanceURL, accessToken }); + await Promise.allSettled([ + initPreferences(client), + initInstance(client, instanceURL), + initAccount(client, instanceURL, accessToken, vapidKey), + ]); + initStates(); + window.__IGNORE_GET_ACCOUNT_ERROR__ = true; - setIsLoggedIn(true); - setUIState('default'); + setIsLoggedIn(true); + setUIState('default'); + } else { + setUIState('error'); + } })(); } else { window.__IGNORE_GET_ACCOUNT_ERROR__ = true; - const account = getCurrentAccount(); + const searchAccount = decodeURIComponent( + (window.location.search.match(/account=([^&]+)/) || [, ''])[1], + ); + let account; + if (searchAccount) { + account = getAccount(searchAccount); + console.log('searchAccount', searchAccount, account); + if (account) { + setCurrentAccountID(account.info.id); + window.history.replaceState( + {}, + document.title, + window.location.pathname || '/', + ); + } + } + if (!account) { + account = getCurrentAccount(); + } if (account) { setCurrentAccountID(account.info.id); const { client } = api({ account }); @@ -365,6 +394,11 @@ function App() { setUIState('default'); } } + + // Cleanup + store.sessionCookie.del('clientID'); + store.sessionCookie.del('clientSecret'); + store.sessionCookie.del('codeVerifier'); }, []); let location = useLocation(); diff --git a/src/components/account-info.jsx b/src/components/account-info.jsx index 4b0c58ce..c036190b 100644 --- a/src/components/account-info.jsx +++ b/src/components/account-info.jsx @@ -63,7 +63,6 @@ const MUTE_DURATIONS_LABELS = { 259_200: i18nDuration(3, 'day'), 604_800: i18nDuration(1, 'week'), }; -console.log({ MUTE_DURATIONS_LABELS }); const LIMIT = 80; diff --git a/src/components/background-service.jsx b/src/components/background-service.jsx index 5b9a7ef1..64144832 100644 --- a/src/components/background-service.jsx +++ b/src/components/background-service.jsx @@ -16,7 +16,18 @@ export default memo(function BackgroundService({ isLoggedIn }) { // Notifications service // - WebSocket to receive notifications when page is visible const [visible, setVisible] = useState(true); - usePageVisibility(setVisible); + const visibleTimeout = useRef(); + usePageVisibility((visible) => { + clearTimeout(visibleTimeout.current); + if (visible) { + setVisible(true); + } else { + visibleTimeout.current = setTimeout(() => { + setVisible(false); + }, POLL_INTERVAL); + } + }); + const checkLatestNotification = async (masto, instance, skipCheckMarkers) => { if (states.notificationsLast) { const notificationsIterator = masto.v1.notifications.list({ diff --git a/src/components/compose.jsx b/src/components/compose.jsx index 7a7fb6de..04c9747c 100644 --- a/src/components/compose.jsx +++ b/src/components/compose.jsx @@ -1154,7 +1154,7 @@ function Compose({ class={`toolbar-button ${ visibility !== 'public' && !sensitive ? 'show-field' : '' } ${visibility !== 'public' ? 'highlight' : ''}`} - title={`Visibility: ${visibility}`} + title={visibility} > ); diff --git a/src/components/nav-menu.css b/src/components/nav-menu.css index 5432e5dd..5a69c777 100644 --- a/src/components/nav-menu.css +++ b/src/components/nav-menu.css @@ -1,7 +1,18 @@ -.nav-menu section:last-child { - background-color: var(--bg-faded-color); - margin-bottom: -8px; - padding-bottom: 8px; +.nav-menu { + overflow: hidden; + + section:last-child { + background-color: var(--bg-faded-color); + margin-bottom: -4px; + padding-bottom: 4px; + + .szh-menu__item:before { + z-index: 0; + } + .szh-menu__item > * { + z-index: 1; + } + } } @media (min-width: 23em) { @@ -13,16 +24,16 @@ 'top top' 'left right'; padding: 0; - width: 22em; + /* min-width: 22em; */ max-width: calc(100vw - 16px); } .nav-menu .top-menu { grid-area: top; - padding-top: 8px; - margin-bottom: -8px; + padding-top: 4px; + margin-bottom: -4px; } .nav-menu section { - padding: 8px 0; + padding: 4px 0; /* width: 50%; */ } @keyframes phanpying { diff --git a/src/components/poll.jsx b/src/components/poll.jsx index 713b92ce..7a8cfe7b 100644 --- a/src/components/poll.jsx +++ b/src/components/poll.jsx @@ -1,4 +1,4 @@ -import { Plural, t, Trans } from '@lingui/macro'; +import { Plural, plural, t, Trans } from '@lingui/macro'; import { useState } from 'preact/hooks'; import shortenNumber from '../utils/shorten-number'; @@ -113,9 +113,10 @@ export default function Poll({
{percentage}
diff --git a/src/components/relative-time.jsx b/src/components/relative-time.jsx index af3b669c..9308c643 100644 --- a/src/components/relative-time.jsx +++ b/src/components/relative-time.jsx @@ -16,7 +16,8 @@ function isValidDate(value) { const resolvedLocale = new Intl.DateTimeFormat().resolvedOptions().locale; const DTF = mem((locale, opts = {}) => { - const lang = localeMatch([locale], [resolvedLocale]); + const regionlessLocale = locale.replace(/-[a-z]+$/i, ''); + const lang = localeMatch([regionlessLocale], [resolvedLocale], locale); try { return new Intl.DateTimeFormat(lang, opts); } catch (e) {} diff --git a/src/components/shortcuts.css b/src/components/shortcuts.css index 99ddf153..d701fd37 100644 --- a/src/components/shortcuts.css +++ b/src/components/shortcuts.css @@ -121,13 +121,31 @@ text-overflow: ellipsis; overflow: hidden; } +#shortcuts .tab-bar li a { + position: relative; + &:before { + content: ''; + position: absolute; + inset: 4px 0; + border-radius: 8px; + background-color: var(--bg-color); + z-index: -1; + transform: scale(0.5); + opacity: 0; + transition: all 0.1s ease-in-out; + } +} #shortcuts .tab-bar li a.is-active { color: var(--link-color); - background-image: radial-gradient( + /* background-image: radial-gradient( closest-side at 50% 50%, var(--bg-color), transparent - ); + ); */ + &:before { + transform: scale(1); + opacity: 1; + } } #app:has(#home-page):not(:has(#home-page ~ .deck-container)):has(header[hidden]) #shortcuts diff --git a/src/components/status.css b/src/components/status.css index 226aaafd..1d6ee4ff 100644 --- a/src/components/status.css +++ b/src/components/status.css @@ -430,6 +430,7 @@ > span + span { position: static; + width: auto; &:empty { display: none; @@ -1895,6 +1896,7 @@ a:focus-visible .card img { .meta-container { align-self: flex-start; flex-grow: 0; + max-width: 100%; } .card .title { line-height: 1.25; diff --git a/src/components/status.jsx b/src/components/status.jsx index 85c72c14..fffd6269 100644 --- a/src/components/status.jsx +++ b/src/components/status.jsx @@ -1061,7 +1061,14 @@ function Status({ )} - {nicePostURL(url)} + + {nicePostURL(url)} + -
+
{uiState === 'start' && (

diff --git a/src/pages/favourites.jsx b/src/pages/favourites.jsx index a68f1613..08b805f2 100644 --- a/src/pages/favourites.jsx +++ b/src/pages/favourites.jsx @@ -22,7 +22,7 @@ function Favourites() { { setFollowUIState('loading'); diff --git a/src/pages/login.jsx b/src/pages/login.jsx index 8956385c..f0a85b42 100644 --- a/src/pages/login.jsx +++ b/src/pages/login.jsx @@ -11,7 +11,12 @@ import LangSelector from '../components/lang-selector'; import Link from '../components/link'; import Loader from '../components/loader'; import instancesListURL from '../data/instances.json?url'; -import { getAuthorizationURL, registerApplication } from '../utils/auth'; +import { + getAuthorizationURL, + getPKCEAuthorizationURL, + registerApplication, +} from '../utils/auth'; +import { supportsPKCE } from '../utils/oauth-pkce'; import store from '../utils/store'; import useTitle from '../utils/useTitle'; @@ -63,17 +68,36 @@ function Login() { instanceURL, }); - if (client_id && client_secret) { - store.session.set('clientID', client_id); - store.session.set('clientSecret', client_secret); - store.session.set('vapidKey', vapid_key); + const authPKCE = await supportsPKCE({ instanceURL }); + console.log({ authPKCE }); + if (authPKCE) { + if (client_id && client_secret) { + store.sessionCookie.set('clientID', client_id); + store.sessionCookie.set('clientSecret', client_secret); + store.sessionCookie.set('vapidKey', vapid_key); - location.href = await getAuthorizationURL({ - instanceURL, - client_id, - }); + const [url, verifier] = await getPKCEAuthorizationURL({ + instanceURL, + client_id, + }); + store.sessionCookie.set('codeVerifier', verifier); + location.href = url; + } else { + alert(t`Failed to register application`); + } } else { - alert('Failed to register application'); + if (client_id && client_secret) { + store.sessionCookie.set('clientID', client_id); + store.sessionCookie.set('clientSecret', client_secret); + store.sessionCookie.set('vapidKey', vapid_key); + + location.href = await getAuthorizationURL({ + instanceURL, + client_id, + }); + } else { + alert(t`Failed to register application`); + } } setUIState('default'); } catch (e) { @@ -158,7 +182,7 @@ function Login() { autocapitalize="off" autocomplete="off" spellCheck={false} - placeholder={`instance domain`} + placeholder={t`instance domain`} onInput={(e) => { setInstanceText(e.target.value); }} diff --git a/src/pages/settings.jsx b/src/pages/settings.jsx index c2fec0e6..43587e93 100644 --- a/src/pages/settings.jsx +++ b/src/pages/settings.jsx @@ -232,9 +232,21 @@ function Settings({ onClose }) {

  • - + + +
    + + + Volunteer translations + + +
  • diff --git a/src/utils/auth.js b/src/utils/auth.js index f03ee345..564ec012 100644 --- a/src/utils/auth.js +++ b/src/utils/auth.js @@ -1,12 +1,32 @@ -const { PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_WEBSITE: WEBSITE } = import.meta - .env; +import { generateCodeChallenge, verifier } from './oauth-pkce'; + +const { + DEV, + PHANPY_CLIENT_NAME: CLIENT_NAME, + PHANPY_WEBSITE: WEBSITE, +} = import.meta.env; const SCOPES = 'read write follow push'; +/* + PHANPY_WEBSITE is set to the default official site. + It's used in pre-built releases, so there's no way to change it dynamically + without rebuilding. + Therefore, we can't use it as redirect_uri. + We only use PHANPY_WEBSITE if it's "same" as current location URL. + + Very basic check based on location.hostname for now +*/ +const sameSite = WEBSITE + ? WEBSITE.toLowerCase().includes(location.hostname) + : false; +const currentLocation = location.origin + location.pathname; +const REDIRECT_URI = DEV || !sameSite ? currentLocation : WEBSITE; + export async function registerApplication({ instanceURL }) { const registrationParams = new URLSearchParams({ client_name: CLIENT_NAME, - redirect_uris: location.origin + location.pathname, + redirect_uris: REDIRECT_URI, scopes: SCOPES, website: WEBSITE, }); @@ -25,11 +45,26 @@ export async function registerApplication({ instanceURL }) { return registrationJSON; } +export async function getPKCEAuthorizationURL({ instanceURL, client_id }) { + const codeVerifier = verifier(); + const codeChallenge = await generateCodeChallenge(codeVerifier); + const params = new URLSearchParams({ + client_id, + code_challenge_method: 'S256', + code_challenge: codeChallenge, + redirect_uri: REDIRECT_URI, + response_type: 'code', + scope: SCOPES, + }); + const authorizationURL = `https://${instanceURL}/oauth/authorize?${params.toString()}`; + return [authorizationURL, codeVerifier]; +} + export async function getAuthorizationURL({ instanceURL, client_id }) { const authorizationParams = new URLSearchParams({ client_id, scope: SCOPES, - redirect_uri: location.origin + location.pathname, + redirect_uri: REDIRECT_URI, // redirect_uri: 'urn:ietf:wg:oauth:2.0:oob', response_type: 'code', }); @@ -42,15 +77,23 @@ export async function getAccessToken({ client_id, client_secret, code, + code_verifier, }) { const params = new URLSearchParams({ client_id, - client_secret, - redirect_uri: location.origin + location.pathname, + redirect_uri: REDIRECT_URI, grant_type: 'authorization_code', code, scope: SCOPES, + // client_secret, + // code_verifier, }); + if (client_secret) { + params.append('client_secret', client_secret); + } + if (code_verifier) { + params.append('code_verifier', code_verifier); + } const tokenResponse = await fetch(`https://${instanceURL}/oauth/token`, { method: 'POST', headers: { diff --git a/src/utils/get-instance-status-url.js b/src/utils/get-instance-status-url.js index 3f4cc6ee..157c08bd 100644 --- a/src/utils/get-instance-status-url.js +++ b/src/utils/get-instance-status-url.js @@ -5,6 +5,7 @@ const statusPostRegexes = [ /^\/@[^@\/]+\/(?:statuses|posts)\/([^\/]+)/i, // GoToSocial, Takahe /\/notes\/([^\/]+)/i, // Misskey, Firefish /^\/(?:notice|objects)\/([a-z0-9-]+)/i, // Pleroma + /\/@[^@\/]+\/post\/([^\/]+)/i, // Threads /\/@[^@\/]+@?[^\/]+?\/([^\/]+)/i, // Mastodon /^\/p\/[^\/]+\/([^\/]+)/i, // Pixelfed ]; diff --git a/src/utils/isMastodonLinkMaybe.jsx b/src/utils/isMastodonLinkMaybe.jsx index ab18c6d4..753fc3bb 100644 --- a/src/utils/isMastodonLinkMaybe.jsx +++ b/src/utils/isMastodonLinkMaybe.jsx @@ -6,7 +6,7 @@ export default function isMastodonLinkMaybe(url) { /^\/(@[^/]+|users\/[^/]+)\/(statuses|posts)\/\w+\/?$/i.test(pathname) || // GoToSocial, Takahe /^\/notes\/[a-z0-9]+$/i.test(pathname) || // Misskey, Firefish /^\/(notice|objects)\/[a-z0-9-]+$/i.test(pathname) || // Pleroma - /^\/@[^/]+\/post\/[a-z0-9]+$/i.test(pathname) || // Threads + /^\/@[^/]+\/post\/[a-z0-9\-_]+$/i.test(pathname) || // Threads /^\/@[^/]+\/[a-z0-9]+[a-z0-9\-]+[a-z0-9]+$/i.test(pathname) || // Hollo (hostname === 'fed.brid.gy' && pathname.startsWith('/r/http')) || // Bridgy Fed /#\/[^\/]+\.[^\/]+\/s\/.+/i.test(hash) // Phanpy 🫣 diff --git a/src/utils/lang.js b/src/utils/lang.js index 5140522b..2ef1b135 100644 --- a/src/utils/lang.js +++ b/src/utils/lang.js @@ -7,7 +7,7 @@ import { } from '@lingui/detect-locale'; import Locale from 'intl-locale-textinfo-polyfill'; -import { DEFAULT_LANG, LOCALES } from '../locales'; +import { ALL_LOCALES, DEFAULT_LANG } from '../locales'; import { messages } from '../locales/en.po'; import localeMatch from '../utils/locale-match'; @@ -62,7 +62,7 @@ export function initActivateLang() { DEFAULT_LANG, ); const matchedLang = - LOCALES.find((l) => l === lang) || localeMatch(lang, LOCALES); + ALL_LOCALES.find((l) => l === lang) || localeMatch(lang, ALL_LOCALES); activateLang(matchedLang); // const yes = confirm(t`Reload to apply language setting?`); diff --git a/src/utils/nice-date-time.js b/src/utils/nice-date-time.js index a1d5baff..adf81c88 100644 --- a/src/utils/nice-date-time.js +++ b/src/utils/nice-date-time.js @@ -1,12 +1,14 @@ import { i18n } from '@lingui/core'; +import localeMatch from './locale-match'; import mem from './mem'; const defaultLocale = new Intl.DateTimeFormat().resolvedOptions().locale; const _DateTimeFormat = (opts) => { const { locale, dateYear, hideTime, formatOpts } = opts || {}; - const loc = locale && !/pseudo/i.test(locale) ? locale : defaultLocale; + const regionlessLocale = locale.replace(/-[a-z]+$/i, ''); + const loc = localeMatch([regionlessLocale], [defaultLocale], locale); const currentYear = new Date().getFullYear(); const options = { // Show year if not current year @@ -20,9 +22,11 @@ const _DateTimeFormat = (opts) => { }; try { return Intl.DateTimeFormat(loc, options); - } catch (e) { - return Intl.DateTimeFormat(undefined, options); - } + } catch (e) {} + try { + return Intl.DateTimeFormat(locale, options); + } catch (e) {} + return Intl.DateTimeFormat(undefined, options); }; const DateTimeFormat = mem(_DateTimeFormat); diff --git a/src/utils/oauth-pkce.js b/src/utils/oauth-pkce.js new file mode 100644 index 00000000..084309b2 --- /dev/null +++ b/src/utils/oauth-pkce.js @@ -0,0 +1,46 @@ +function dec2hex(dec) { + return ('0' + dec.toString(16)).slice(-2); +} +export function verifier() { + var array = new Uint32Array(56 / 2); + window.crypto.getRandomValues(array); + return Array.from(array, dec2hex).join(''); +} +function sha256(plain) { + // returns promise ArrayBuffer + const encoder = new TextEncoder(); + const data = encoder.encode(plain); + return window.crypto.subtle.digest('SHA-256', data); +} +function base64urlencode(a) { + let str = ''; + const bytes = new Uint8Array(a); + const len = bytes.byteLength; + for (var i = 0; i < len; i++) { + str += String.fromCharCode(bytes[i]); + } + return btoa(str).replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, ''); +} +export async function generateCodeChallenge(v) { + const hashed = await sha256(v); + return base64urlencode(hashed); +} + +// If /.well-known/oauth-authorization-server exists and code_challenge_methods_supported includes "S256", means support PKCE +export async function supportsPKCE({ instanceURL }) { + if (!instanceURL) return false; + try { + const res = await fetch( + `https://${instanceURL}/.well-known/oauth-authorization-server`, + ); + if (!res.ok || res.status !== 200) return false; + const json = await res.json(); + if (json.code_challenge_methods_supported?.includes('S256')) return true; + return false; + } catch (e) { + return false; + } +} + +// For debugging +window.__generateCodeChallenge = generateCodeChallenge; diff --git a/src/utils/push-notifications.js b/src/utils/push-notifications.js index 598e54fe..b4621e51 100644 --- a/src/utils/push-notifications.js +++ b/src/utils/push-notifications.js @@ -1,6 +1,6 @@ // Utils for push notifications import { api } from './api'; -import { getCurrentAccount } from './store-utils'; +import { getVapidKey } from './store-utils'; // Subscription is an object with the following structure: // { @@ -113,7 +113,7 @@ export async function initSubscription() { // Check if the subscription changed if (backendSubscription && subscription) { const sameEndpoint = backendSubscription.endpoint === subscription.endpoint; - const { vapidKey } = getCurrentAccount(); + const vapidKey = getVapidKey(); const sameKey = backendSubscription.serverKey === vapidKey; if (!sameEndpoint) { throw new Error('Backend subscription endpoint changed'); @@ -146,7 +146,7 @@ export async function initSubscription() { if (subscription && !backendSubscription) { // check if account's vapidKey is same as subscription's applicationServerKey - const { vapidKey } = getCurrentAccount(); + const vapidKey = getVapidKey(); if (vapidKey) { const { applicationServerKey } = subscription.options; const vapidKeyStr = urlBase64ToUint8Array(vapidKey).toString(); @@ -210,7 +210,7 @@ export async function updateSubscription({ data, policy }) { } } else { // User is not subscribed - const { vapidKey } = getCurrentAccount(); + const vapidKey = getVapidKey(); if (!vapidKey) throw new Error('No server key found'); subscription = await registration.pushManager.subscribe({ userVisibleOnly: true, diff --git a/src/utils/store-utils.js b/src/utils/store-utils.js index 042b2fa0..cc236215 100644 --- a/src/utils/store-utils.js +++ b/src/utils/store-utils.js @@ -154,6 +154,13 @@ export function getCurrentInstanceConfiguration() { return getInstanceConfiguration(instance); } +export function getVapidKey() { + // Vapid key has moved from account to instance config + const config = getCurrentInstanceConfiguration(); + const vapidKey = config?.vapid?.publicKey || config?.vapid?.public_key; + return vapidKey || getCurrentAccount()?.vapidKey; +} + export function isMediaFirstInstance() { const instance = getCurrentInstance(); return /pixelfed/i.test(instance?.version); diff --git a/src/utils/store.js b/src/utils/store.js index fb587b11..19309885 100644 --- a/src/utils/store.js +++ b/src/utils/store.js @@ -1,5 +1,9 @@ +import Cookies from 'js-cookie'; + import { getCurrentAccountNS } from './store-utils'; +const cookies = Cookies.withAttributes({ sameSite: 'strict', secure: true }); + const local = { get: (key) => { try { @@ -86,6 +90,38 @@ const session = { }, }; +// Session secure cookie +const cookie = { + get: (key) => cookies.get(key), + set: (key, value) => cookies.set(key, value), + del: (key) => cookies.remove(key), +}; + +// Cookie with sessionStorage fallback +const sessionCookie = { + get: (key) => { + if (navigator.cookieEnabled) { + return cookie.get(key); + } else { + return session.get(key); + } + }, + set: (key, value) => { + if (navigator.cookieEnabled) { + return cookie.set(key, value); + } else { + return session.set(key, value); + } + }, + del: (key) => { + if (navigator.cookieEnabled) { + return cookie.del(key); + } else { + return session.del(key); + } + }, +}; + // Store with account namespace (id@domain.tld) <- uses id, not username const account = { get: (key) => { @@ -118,4 +154,4 @@ const account = { }, }; -export default { local, session, account }; +export default { local, session, sessionCookie, cookie, account }; diff --git a/vite.config.js b/vite.config.js index fc085f66..ed3dbc1d 100644 --- a/vite.config.js +++ b/vite.config.js @@ -12,9 +12,12 @@ import { VitePWA } from 'vite-plugin-pwa'; import removeConsole from 'vite-plugin-remove-console'; import { run } from 'vite-plugin-run'; +import { ALL_LOCALES } from './src/locales'; + const allowedEnvPrefixes = ['VITE_', 'PHANPY_']; const { NODE_ENV } = process.env; const { + PHANPY_WEBSITE: WEBSITE, PHANPY_CLIENT_NAME: CLIENT_NAME, PHANPY_APP_ERROR_LOGGING: ERROR_LOGGING, } = loadEnv('production', process.cwd(), allowedEnvPrefixes); @@ -70,6 +73,11 @@ export default defineConfig({ run: ['npm', 'run', 'messages:extract:clean'], pattern: 'src/**/*.{js,jsx,ts,tsx}', }, + // { + // name: 'update-catalogs', + // run: ['node', 'scripts/catalogs.js'], + // pattern: 'src/locales/*.po', + // }, ], }), splitVendorChunkPlugin(), @@ -78,6 +86,20 @@ export default defineConfig({ }), htmlPlugin({ headScripts: ERROR_LOGGING ? [rollbarCode] : [], + links: [ + ...ALL_LOCALES.map((lang) => ({ + rel: 'alternate', + hreflang: lang, + // *Fully-qualified* URLs + href: `${WEBSITE}/?lang=${lang}`, + })), + // https://developers.google.com/search/docs/specialty/international/localized-versions#xdefault + { + rel: 'alternate', + hreflang: 'x-default', + href: `${WEBSITE}`, + }, + ], }), generateFile([ {