diff options
| author | dakkar <dakkar@thenautilus.net> | 2024-12-15 17:27:12 +0000 |
|---|---|---|
| committer | dakkar <dakkar@thenautilus.net> | 2024-12-15 17:27:12 +0000 |
| commit | e2352839e4639b09e2e52b2ada1399097fad1d8d (patch) | |
| tree | 9268cda477b8c1dcfb2c78eaabcb173a1566a469 | |
| parent | merge: Fix rate limits under multi-node environments (!809) (diff) | |
| parent | upstream merge checklist: remember to check federated profile fields (diff) | |
| download | sharkey-e2352839e4639b09e2e52b2ada1399097fad1d8d.tar.gz sharkey-e2352839e4639b09e2e52b2ada1399097fad1d8d.tar.bz2 sharkey-e2352839e4639b09e2e52b2ada1399097fad1d8d.zip | |
merge: upstream changes for 2024.11 (!742)
View MR for information: https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/742
Closes #645 and #646
Approved-by: Hazelnoot <acomputerdog@gmail.com>
Approved-by: Marie <github@yuugi.dev>
616 files changed, 16748 insertions, 7914 deletions
diff --git a/.config/cypress-devcontainer.yml b/.config/cypress-devcontainer.yml index 342b0f43da..c8c4a36d8f 100644 --- a/.config/cypress-devcontainer.yml +++ b/.config/cypress-devcontainer.yml @@ -2,6 +2,19 @@ # Misskey configuration #â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â”â” +# ┌────────────────────────┠+#───┘ Initial Setup Password └───────────────────────────────────────────────────── + +# Password to initiate setting up admin account. +# It will not be used after the initial setup is complete. +# +# Be sure to change this when you set up Misskey via the Internet. +# +# The provider of the service who sets up Misskey on behalf of the customer should +# set this value to something unique when generating the Misskey config file, +# and provide it to the customer. +setupPassword: example_password_please_change_this_or_you_will_get_hacked + # ┌─────┠#───┘ URL └───────────────────────────────────────────────────── diff --git a/.config/example.yml b/.config/example.yml index 9debb3bf70..ba8e818b5d 100644 --- a/.config/example.yml +++ b/.config/example.yml @@ -59,6 +59,20 @@ # # publishTarballInsteadOfProvideRepositoryUrl: true +# ┌────────────────────────┠+#───┘ Initial Setup Password └───────────────────────────────────────────────────── + +# Password to initiate setting up admin account. +# It will not be used after the initial setup is complete. +# +# Be sure to change this when you set up Misskey via the Internet. +# +# The provider of the service who sets up Misskey on behalf of the customer should +# set this value to something unique when generating the Misskey config file, +# and provide it to the customer. +# +# setupPassword: example_password_please_change_this_or_you_will_get_hacked + # ┌─────┠#───┘ URL └───────────────────────────────────────────────────── diff --git a/.config/test-example.yml b/.config/test-example.yml new file mode 100644 index 0000000000..713cbdb0a0 --- /dev/null +++ b/.config/test-example.yml @@ -0,0 +1,17 @@ +url: 'http://misskey.local' + +setupPassword: example_password_please_change_this_or_you_will_get_hacked + +# ãƒãƒ¼ã‚«ãƒ«ã§ãƒ†ã‚¹ãƒˆã™ã‚‹ã¨ãã«ãƒãƒ¼ãƒˆã‚’被らãªã„よã†ã«ã™ã‚‹ãŸã‚デフォルトã®ã‚‚ã®ã¨ã¯å¤‰ãˆã‚‹(以下åŒã˜) +port: 61812 + +db: + host: 127.0.0.1 + port: 5432 + db: test-misskey + user: postgres + pass: '' +redis: + host: 127.0.0.1 + port: 6379 +id: aidx diff --git a/.devcontainer/devcontainer.json b/.devcontainer/devcontainer.json index fbf959d449..713c2e5fdd 100644 --- a/.devcontainer/devcontainer.json +++ b/.devcontainer/devcontainer.json @@ -5,7 +5,7 @@ "workspaceFolder": "/workspace", "features": { "ghcr.io/devcontainers/features/node:1": { - "version": "20.16.0" + "version": "22.11.0" }, "ghcr.io/devcontainers-contrib/features/corepack:1": {} }, diff --git a/.gitignore b/.gitignore index 7cc7354a4a..b07d195a3f 100644 --- a/.gitignore +++ b/.gitignore @@ -36,11 +36,12 @@ coverage # config /.config/* !/.config/example.yml +!/.config/test-example.yml !/.config/docker_example.yml !/.config/docker_example.env !/.config/cypress-devcontainer.yml docker-compose.yml -compose.yml +./compose.yml .devcontainer/compose.yml !/.devcontainer/compose.yml @@ -71,6 +72,8 @@ misskey-assets # Vite temporary files vite.config.js.timestamp-* vite.config.ts.timestamp-* +vite.config.local-dev.js.timestamp-* +vite.config.local-dev.ts.timestamp-* # blender backups *.blend1 diff --git a/.node-version b/.node-version index 8ce7030825..7af24b7ddb 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -20.16.0 +22.11.0 diff --git a/CHANGELOG.md b/CHANGELOG.md index cf0437e51a..2dbc457710 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,134 @@ +## 2024.11.0 + +### Note +- Node.js 20.xã¯éžæŽ¨å¥¨ã«ãªã‚Šã¾ã—ãŸã€‚Node.js 22.x (LTS)ã®åˆ©ç”¨ã‚’推奨ã—ã¾ã™ã€‚ + - ãªãŠã€Node.js 23.xã¯å¯¾å¿œã—ã¦ã„ã¾ã›ã‚“。 +- Dockerã®Node.jsãŒ22.11.0ã«æ›´æ–°ã•れã¾ã—㟠+ +### General +- Feat: コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã§ãるよã†ã« +- Feat: éŽåŽ»ã®ãƒŽãƒ¼ãƒˆã‚’éžå…¬é–‹åŒ–/フォãƒãƒ¯ãƒ¼ã®ã¿è¡¨ç¤ºå¯èƒ½ã«ã§ãるよã†ã« +- Enhance: ä¾å˜é–¢ä¿‚ã®æ›´æ–° +- Enhance: l10nã®æ›´æ–° +- Fix: ãŠçŸ¥ã‚‰ã›ä½œæˆæ™‚ã«ç”»åƒURL入力欄を空欄ã«å¤‰æ›´ã§ããªã„ã®ã‚’ä¿®æ£ ( #14976 ) + +### Client +- Enhance: Bull Dashboardã§Relationship Queueã®çŠ¶æ…‹ã‚‚ç¢ºèªã§ãるよã†ã« + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/751) +- Enhance: ドライブã§ã‚½ãƒ¼ãƒˆãŒã§ãるよã†ã« +- Enhance: アイコンデコレーション管ç†ç”»é¢ã®æ”¹å–„ +- Enhance: 「å˜ãªã‚‹ãƒ©ãƒƒã‚ーã€ã®å–å¾—æ¡ä»¶ã‚’変更 +- Enhance: 投稿フォームã§Escã‚ーを押ã—ãŸã¨ãIME入力ä¸ãªã‚‰ãƒ•ォームを閉ã˜ãªã„よã†ã«ï¼ˆ #10866 ) +- Enhance: MiAuth, OAuthã®èªå¯ç”»é¢ã®æ”¹å–„ + - ã©ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã§èªè¨¼ã—よã†ã¨ã—ã¦ã„ã‚‹ã®ã‹ãŒã‚ã‹ã‚‹ã‚ˆã†ã« + - èªè¨¼ã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’切り替ãˆã‚‰ã‚Œã‚‹ã‚ˆã†ã« +- Enhance: Self-XSS防æ¢ç”¨ã®è¦å‘Šã‚’è¿½åŠ +- Enhance: カタルーニャ語 (ca-ES) ã«å¯¾å¿œ +- Enhance: 個別ãŠçŸ¥ã‚‰ã›ãƒšãƒ¼ã‚¸ã§ã¯Metaタグを出力ã™ã‚‹ã‚ˆã†ã« +- Enhance: ノート詳細画é¢ã«ãƒãƒ¼ãƒ«ã®ãƒãƒƒã‚¸ã‚’表示 +- Enhance: éŽåŽ»ã«é€ä¿¡ã—ãŸãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’確èªã§ãるよã†ã« + (Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/663) +- Enhance: サイドãƒãƒ¼ã‚’ç°¡å˜ã«å±•開・折りãŸãŸã¿ã§ãるよã†ã« ( #14981 ) +- Enhance: リノートメニューã«ã€ŒãƒªãƒŽãƒ¼ãƒˆã®è©³ç´°ã€ã‚’è¿½åŠ +- Enhance: éžãƒã‚°ã‚¤ãƒ³çŠ¶æ…‹ã§Misskeyã‚’é–‹ã„ãŸéš›ã®ãƒ‘フォーマンスをå‘上 +- Fix: 通知ã®ç¯„囲指定ã®è¨å®šé …ç›®ãŒå¿…è¦ãªã„通知è¨å®šã§ã‚‚範囲指定ã®è¨å®šãŒã§ã¦ã„ã‚‹å•é¡Œã‚’ä¿®æ£ +- Fix: TurnstileãŒå¤±æ•—・期é™åˆ‡ã‚Œã—ãŸéš›ã«ã‚‚æˆåŠŸæ‰±ã„ã¨ãªã£ã¦ã—ã¾ã†å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/768) +- Fix: デッã‚ã®ã‚¿ã‚¤ãƒ ラインカラムã§ã€Œã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ãªãƒ•ァイルをå«ã‚€ãƒŽãƒ¼ãƒˆã‚’表示ã€è¨å®šãŒä½¿ç”¨ã§ããªã‹ã£ãŸå•é¡Œã‚’ä¿®æ£ +- Fix: Encode RSS urls with escape sequences before fetching allowing query parameters to be used +- Fix: ãƒªãƒ³ã‚¯åˆ‡ã‚Œã‚’ä¿®æ£ += Fix: ノート投稿ボタンã«ãƒ›ãƒãƒ¼æ™‚ã®ã‚¹ã‚¿ã‚¤ãƒ«ãŒé©ç”¨ã•れã¦ã„ãªã„ã®ã‚’ä¿®æ£ + (Cherry-picked from https://github.com/taiyme/misskey/pull/305) +- Fix: メールアドレス登録有効化時ã®ã€Œå®Œäº†ã€ãƒ€ã‚¤ã‚¢ãƒã‚°ãƒœãƒƒã‚¯ã‚¹ã®è¡¨ç¤ºæ¡ä»¶ã‚’ä¿®æ£ +- Fix: ç”»é¢å¹…ãŒç‹ã„環境ã§ãƒ‡ã‚¶ã‚¤ãƒ³ãŒå´©ã‚Œã‚‹å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://github.com/MisskeyIO/misskey/pull/815) +- Fix: TypeScriptã®åž‹ãƒã‚§ãƒƒã‚¯å¯¾è±¡ãƒ•ァイルをé™å®šã—ã¦ãƒ“ルドを高速化ã™ã‚‹ã‚ˆã†ã« + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/725) + +### Server +- Enhance: Dockerã®Node.jsã‚’22.11.0ã«æ›´æ–° +- Enhance: èµ·å‹•å‰ã®ç–Žé€šãƒã‚§ãƒƒã‚¯ã§ã€DBã¨ãƒ¡ã‚¤ãƒ³ä»¥å¤–ã®Redisã®ç–Žé€šç¢ºèªã‚‚行ã†ã‚ˆã†ã« + (Based on https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/588) + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/715) +- Enhance: リモートユーザーã®ç…§ä¼šã‚’オリジナルã«ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã™ã‚‹ã‚ˆã†ã« +- Fix: sharedInboxãŒç„¡ã„Actorã«ç´ã¥ãリモートユーザーを照会ã§ããªã„ +- Fix: Aproving request from GtS appears with some delay +- Fix: フォãƒãƒ¯ãƒ¼ã¸ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã®çµµæ–‡å—ã‚’emojisã«å«ã‚るよã†ã« +- Fix: Nested proxy requestsを検出ã—ãŸéš›ã«ãƒ–ãƒãƒƒã‚¯ã™ã‚‹ã‚ˆã†ã« + [ghsa-gq5q-c77c-v236](https://github.com/misskey-dev/misskey/security/advisories/ghsa-gq5q-c77c-v236) +- Fix: 招待コードã®ç™ºè¡Œå¯èƒ½ãªæ®‹ã‚Šæ•°ç®—出ã«ä½¿ç”¨ã™ã¹ããƒãƒ¼ãƒ«ãƒãƒªã‚·ãƒ¼ã®å€¤ãŒé•ã†å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/706) +- Fix: 連åˆã¸ã®é…信時ã«ã€acctã®å¤§å°æ–‡å—ãŒåŒºåˆ¥ã•れã¦ã—ã¾ã„æ£ã—ãメンションãŒå‡¦ç†ã•れãªã„ã“ã¨ãŒã‚ã‚‹å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/711) +- Fix: ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¸ã®ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ã‚’å«ã‚€ãƒŽãƒ¼ãƒˆãŒé€£åˆã•ã‚Œã‚‹éš›ã«æ£ã—ã„URLã«å¤‰æ›ã•れãªã„ã“ã¨ãŒã‚ã‚‹å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/712) +- Fix: FTT無効時ã«ãƒ¦ãƒ¼ã‚¶ãƒ¼ãƒªã‚¹ãƒˆã‚¿ã‚¤ãƒ ラインãŒä½¿ç”¨ã§ããªã„å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/709) +- Fix: User Webhookテスト機能ã®Mock Payloadã‚’ä¿®æ£ +- Fix: アカウント削除ã®ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãƒã‚°ãŒå‹•作ã—ã¦ã„ãªã„ã®ã‚’ä¿®æ£ (#14996) +- Fix: ãƒªãƒŽãƒ¼ãƒˆãƒŸãƒ¥ãƒ¼ãƒˆãŒæ–°è¦æŠ•稿通知ã«å¯¾ã—ã¦ä½œç”¨ã—ã¦ã„ãªã‹ã£ãŸå•é¡Œã‚’ä¿®æ£ +- Fix: Inboxã®å‡¦ç†ã§ç”Ÿã˜ã‚‹ã‚¨ãƒ©ãƒ¼ã‚’誤ã£ã¦Activityã¨ã—ã¦å‡¦ç†ã™ã‚‹ã“ã¨ãŒã‚ã‚‹å•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/730) +- Fix: ã‚»ã‚ュリティã«é–¢ã™ã‚‹ä¿®æ£ + +### Misskey.js +- Fix: StreamåˆæœŸåŒ–時ã€åˆ¥é€”WebSocketを指定ã™ã‚‹å ´åˆã®åž‹å®šç¾©ã‚’ä¿®æ£ + +## 2024.10.1 + +### Note +- スパム対ç–ã¨ã—ã¦ã€ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿æ¨©é™ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティãŒ7日以上確èªã§ããªã„å ´åˆã¯è‡ªå‹•çš„ã«æ‹›å¾…制ã¸ã¨åˆ‡ã‚Šæ›¿ãˆï¼ˆã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ« -> モデレーション -> "誰ã§ã‚‚æ–°è¦ç™»éŒ²ã§ãるよã†ã«ã™ã‚‹"をオフã«å¤‰æ›´ï¼‰ã‚‹ã‚ˆã†ã«ãªã‚Šã¾ã—ãŸã€‚ ( #13437 ) + - 切り替ã‚ã£ãŸéš›ã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¸ãŠçŸ¥ã‚‰ã›ã¨ã—ã¦é€šçŸ¥ã•れã¾ã™ã€‚登録をオープンãªçŠ¶æ…‹ã§ç¶™ç¶šã—ãŸã„å ´åˆã¯ã€ã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ãƒ‘ãƒãƒ«ã‹ã‚‰å†åº¦è¨å®šã‚’行ã£ã¦ãã ã•ã„。 + +### General +- Feat: ユーザーã®åå‰ã«ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ã‚’è¨å®šã§ãるよã†ã« + +### Client +- Enhance: タイムライン表示時ã®ãƒ‘フォーマンスをå‘上 +- Enhance: アーカイブã—ãŸå€‹äººå®›ã®ãŠçŸ¥ã‚‰ã›ã‚’表示・編集ã§ãるよã†ã« +- Enhance: l10nã®æ›´æ–° +- Fix: メールアドレスä¸è¦ã§CaptchaãŒæœ‰åйãªå ´åˆã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆç™»éŒ²å®Œäº†å¾Œè‡ªå‹•ã§ã®ãƒã‚°ã‚¤ãƒ³ã«å¤±æ•—ã™ã‚‹å•é¡Œã‚’ä¿®æ£ + +### Server +- Feat: モデレータ権é™ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ãŒå…¨å“¡7日間活動ã—ãªã‹ã£ãŸå ´åˆã¯è‡ªå‹•çš„ã«æ‹›å¾…制ã¸ã¨åˆ‡ã‚Šæ›¿ãˆã‚‹ã‚ˆã†ã« ( #13437 ) +- Enhance: 個人宛ã®ãŠçŸ¥ã‚‰ã›ã¯ã€Œã‚ã‹ã£ãŸã€ã‚’押ã™ã¨è‡ªå‹•çš„ã«ã‚¢ãƒ¼ã‚«ã‚¤ãƒ–ã•れるよã†ã« +- Fix: `admin/emoji/update`エンドãƒã‚¤ãƒ³ãƒˆã®idã®ã¿æŒ‡å®šã—ãŸæ™‚䏿£ãªã‚¨ãƒ©ãƒ¼ãŒç™ºç”Ÿã™ã‚‹ãƒã‚°ã‚’ä¿®æ£ +- Fix: RBT有効時ã€ãƒªãƒŽãƒ¼ãƒˆã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ãŒåæ˜ ã•れãªã„å•é¡Œã‚’ä¿®æ£ +- Fix: ã‚ューã®ã‚¨ãƒ©ãƒ¼ãƒã‚°ã‚’簡略化ã™ã‚‹ã‚ˆã†ã« + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/649) + +## 2024.10.0 + +### Note +- ã‚»ã‚ュリティå‘上ã®ãŸã‚ã€ã‚µãƒ¼ãƒãƒ¼åˆæœŸè¨å®šæ™‚ã«ä½¿ç”¨ã™ã‚‹åˆæœŸãƒ‘スワードをè¨å®šã§ãるよã†ã«ãªã‚Šã¾ã—ãŸã€‚今後Misskeyサーãƒãƒ¼ã‚’æ–°ãŸã«è¨ç½®ã™ã‚‹éš›ã«ã¯ã€åˆå›žã®èµ·å‹•å‰ã«ã‚³ãƒ³ãƒ•ィグファイルã®`setupPassword`をコメントアウトã—ã€åˆæœŸãƒ‘スワードをè¨å®šã™ã‚‹ã“ã¨ã‚’ãŠã™ã™ã‚ã—ã¾ã™ã€‚(ã™ã§ã«åˆæœŸè¨å®šã‚’完了ã—ã¦ã„るサーãƒãƒ¼ã«ã¤ã„ã¦ã¯ã€ã“ã®å¤‰æ›´ã«ä¼´ã„対応ã™ã‚‹å¿…è¦ã¯ã‚りã¾ã›ã‚“) + - ホスティングサービスをé‹å–¶ã—ã¦ã„ã‚‹å ´åˆã¯ã€ã‚³ãƒ³ãƒ•ィグファイルを構築ã™ã‚‹éš›ã«`setupPassword`をランダムãªå€¤ã«è¨å®šã—ã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã«é€šçŸ¥ã™ã‚‹ã‚ˆã†ã«ã‚·ã‚¹ãƒ†ãƒ ã‚’æ›´æ–°ã™ã‚‹ã“ã¨ã‚’ãŠã™ã™ã‚ã—ã¾ã™ã€‚ + - ãªãŠã€åˆæœŸãƒ‘スワードãŒè¨å®šã•れã¦ã„ãªã„å ´åˆã§ã‚‚åˆæœŸè¨å®šã‚’行ã†ã“ã¨ãŒå¯èƒ½ã§ã™ï¼ˆUI上ã§åˆæœŸãƒ‘スワードã®å…¥åŠ›æ¬„ã‚’ç©ºæ¬„ã«ã™ã‚‹ã¨ç¶šè¡Œã§ãã¾ã™ï¼‰ã€‚ +- ユーザーデータをèªã¿è¾¼ã‚€éš›ã®åž‹ãŒä¸€éƒ¨å¤‰æ›´ã•れã¾ã—ãŸã€‚ + - `twoFactorEnabled`, `usePasswordLessLogin`, `securityKeys`: 自分ã¨ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ä»¥å¤–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰ã¯å–å¾—ã§ããªããªã‚Šã¾ã—㟠+ +### General +- Feat: サーãƒãƒ¼åˆæœŸè¨å®šæ™‚ã«åˆæœŸãƒ‘スワードをè¨å®šã§ãるよã†ã« +- Feat: é€šå ±ã«ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãƒŽãƒ¼ãƒˆã‚’残ã›ã‚‹ã‚ˆã†ã« +- Feat: é€šå ±ã®è§£æ±ºç¨®åˆ¥ã‚’è¨å®šã§ãるよã†ã« +- Enhance: é€šå ±ã®è§£æ±ºã¨è»¢é€ã‚’個別ã«è¡Œãˆã‚‹ã‚ˆã†ã« +- Enhance: ã‚»ã‚ュリティå‘上ã®ãŸã‚ã€ã‚µã‚¤ãƒ³ã‚¤ãƒ³æ™‚ã‚‚CAPTCHAを求ã‚るよã†ã«ãªã‚Šã¾ã—㟠+- Enhance: ä¾å˜é–¢ä¿‚ã®æ›´æ–° +- Enhance: l10nã®æ›´æ–° +- Enhance: Playã®ã€Œäººæ°—ã€ã‚¿ãƒ–ã§10件以上表示å¯èƒ½ã« #14399 +- Fix: 連åˆã®ãƒ›ãƒ¯ã‚¤ãƒˆãƒªã‚¹ãƒˆãŒæ£å¸¸ã«ç™»éŒ²ã•れãªã„å•é¡Œã‚’ä¿®æ£ + +### Client +- Enhance: デザインã®èª¿æ•´ +- Enhance: ãƒã‚°ã‚¤ãƒ³ç”»é¢ã®èªè¨¼ãƒ•ãƒãƒ¼ã‚’改善 +- Fix: クライアント上ã§ã®æ™‚間ベースã®å®Ÿç¸¾ç²å¾—動作ãŒå®Ÿç¸¾ç²å¾—後も発動ã—ã¦ã„ãŸå•é¡Œã‚’ä¿®æ£ + (Cherry-picked from https://activitypub.software/TransFem-org/Sharkey/-/merge_requests/657) + +### Server +- Enhance: ã‚»ã‚ュリティå‘上ã®ãŸã‚ã€ãƒã‚°ã‚¤ãƒ³æ™‚ã«ãƒ¡ãƒ¼ãƒ«é€šçŸ¥ã‚’行ã†ã‚ˆã†ã« +- Enhance: 自分ã¨ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ä»¥å¤–ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã‹ã‚‰äºŒè¦ç´ èªè¨¼é–¢é€£ã®ãƒ‡ãƒ¼ã‚¿ãŒå–å¾—ã§ããªã„よã†ã« +- Enhance: é€šå ±ãŠã‚ˆã³é€šå ±è§£æ±ºæ™‚ã«é€å‡ºã•れるSystemWebhookã«ãƒ¦ãƒ¼ã‚¶æƒ…å ±ã‚’å«ã‚るよã†ã« ( #14697 ) +- Fix: `admin/abuse-user-reports`エンドãƒã‚¤ãƒ³ãƒˆã®ã‚¹ã‚ーマãŒé–“é•ã£ã¦ã„ãŸå•é¡Œã‚’ä¿®æ£ + ## 2024.9.0 ### General diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index f2e48ec61d..6b01135d11 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -62,9 +62,29 @@ Thank you for your PR! Before creating a PR, please check the following: Thanks for your cooperation 🤗 +### Additional things for ActivityPub payload changes +*This section is specific to misskey-dev implementation. Other fork or implementation may take different way. A significant difference is that non-"misskey-dev" extension is not described in the misskey-hub's document.* + +If PR includes changes to ActivityPub payload, please reflect it in [misskey-hub's document](https://github.com/misskey-dev/misskey-hub-next/blob/master/content/ns.md) by sending PR. + +The name of purporsed extension property (referred as "extended property" in later) to ActivityPub shall be prefixed by `_misskey_`. (i.e. `_misskey_quote`) + +The extended property in `packages/backend/src/core/activitypub/type.ts` **must** be declared as optional because ActivityPub payloads that comes from older Misskey or other implementation may not contain it. + +The extended property must be included in the context definition. Context is defined in `packages/backend/src/core/activitypub/misc/contexts.ts`. +The key shall be same as the name of extended property, and the value shall be same as "short IRI". + +"Short IRI" is defined in misskey-hub's document, but usually takes form of `misskey:<name of extended property>`. (i.e. `misskey:_misskey_quote`) + +One should not add property that has defined before by other implementation, or add custom variant value to "well-known" property. + ## Reviewers guide Be willing to comment on the good points and not just the things you want fixed 💯 +èªã‚“ã§ãŠãã¨ã„ã„や㤠+- https://blog.lacolaco.net/posts/1e2cf439b3c2/ +- https://konifar-zatsu.hatenadiary.jp/entry/2024/11/05/192421 + ### Review perspective - Scope - Are the goals of the PR clear? @@ -79,6 +99,29 @@ Be willing to comment on the good points and not just the things you want fixed - Are there any omissions or gaps? - Does it check for anomalies? +## Security Advisory +### For reporter +Thank you for your reporting! + +If you can also create a patch to fix the vulnerability, please create a PR on the private fork. + +> [!note] +> There is a GitHub bug that prevents merging if a PR not following the develop branch of upstream, so please keep follow the develop branch. + +### For misskey-dev member +ä¿®æ£PRãŒdevelopã«è¿½å¾“ã•れã¦ã„ãªã„ã¨ãƒžãƒ¼ã‚¸ã§ããªã„ã®ã§ã€ãƒžãƒ¼ã‚¸ã§ããªã‹ã£ãŸã‚‰ + +> Could you merge or rebase onto upstream develop branch? + +ãªã©ã¨ä¼ãˆã‚‹ã€‚ + +## Deploy +The `/deploy` command by issue comment can be used to deploy the contents of a PR to the preview environment. +``` +/deploy sha=<commit hash> +``` +An actual domain will be assigned so you can test the federation. + ## Merge ## Release @@ -107,7 +150,8 @@ You can improve our translations with your Crowdin account. Your changes in Crowdin are automatically submitted as a PR (with the title "New Crowdin translations") to the repository. The owner [@syuilo](https://github.com/syuilo) merges the PR into the develop branch before the next release. -If your language is not listed in Crowdin, please open an issue. +If your language is not listed in Crowdin, please open an issue. We will add it to Crowdin. +For newly added languages, once the translation progress per language exceeds 70%, it will be officially introduced into Misskey and made available to users.  @@ -181,31 +225,46 @@ MK_DEV_PREFER=backend pnpm dev - HMR may not work in some environments such as Windows. ## Testing -- Test codes are located in [`/packages/backend/test`](packages/backend/test). -### Run test -Create a config file. -``` -cp .github/misskey/test.yml .config/ -``` -Prepare DB/Redis for testing. -``` -docker compose -f packages/backend/test/compose.yml up +You can run non-backend tests by executing following commands: +```sh +pnpm --filter frontend test +pnpm --filter misskey-js test ``` -Alternatively, prepare an empty (data can be erased) DB and edit `.config/test.yml`. -Run all test. +Backend tests require manual preparation of servers. See the next section for more on this. + +### Backend +There are three types of test codes for the backend: +- Unit tests: [`/packages/backend/test/unit`](/packages/backend/test/unit) +- Single-server E2E tests: [`/packages/backend/test/e2e`](/packages/backend/test/e2e) +- Multiple-server E2E tests: [`/packages/backend/test-federation`](/packages/backend/test-federation) + +#### Running Unit Tests or Single-server E2E Tests +1. Create a config file: +```sh +cp .config/test-example.yml .config/test.yml ``` -pnpm test + +2. Start DB and Redis servers for testing: +```sh +docker compose -f packages/backend/test/compose.yml up ``` +Instead, you can prepare an empty (data can be erased) DB and edit `.config/test.yml` appropriately. -#### Run specify test +3. Run all tests: +```sh +pnpm --filter backend test # unit tests +pnpm --filter backend test:e2e # single-server E2E tests ``` -pnpm jest -- foo.ts +If you want to run a specific test, run as a following command: +```sh +pnpm --filter backend test -- packages/backend/test/unit/activitypub.ts +pnpm --filter backend test:e2e -- packages/backend/test/e2e/nodeinfo.ts ``` -### e2e tests -TODO +#### Running Multiple-server E2E Tests +See [`/packages/backend/test-federation/README.md`](/packages/backend/test-federation/README.md). ## Environment Variable @@ -579,19 +638,19 @@ ESMã§ã¯ãƒ‡ã‚£ãƒ¬ã‚¯ãƒˆãƒªã‚¤ãƒ³ãƒãƒ¼ãƒˆã¯å»ƒæ¢ã•れã¦ã„ã‚‹ã®ã¨ã€ãƒ‡ã‚ ### Lighten CSS vars ``` css -color: hsl(from var(--accent) h s calc(l + 10)); +color: hsl(from var(--MI_THEME-accent) h s calc(l + 10)); ``` ### Darken CSS vars ``` css -color: hsl(from var(--accent) h s calc(l - 10)); +color: hsl(from var(--MI_THEME-accent) h s calc(l - 10)); ``` ### Add alpha to CSS vars ``` css -color: color(from var(--accent) srgb r g b / 0.5); +color: color(from var(--MI_THEME-accent) srgb r g b / 0.5); ``` ## Merging from Misskey into Sharkey @@ -615,7 +674,7 @@ seems to do a decent job) `packages/backend/src/core/activitypub/models/ApNoteService.ts`, from `createNote` to `updateNote` * from `packages/backend/src/core/NoteCreateService.ts` to - `packages/backend/src/core/NoteEditService.vue` + `packages/backend/src/core/NoteEditService.ts` * from `packages/backend/src/server/api/endpoints/notes/create.ts` to `packages/backend/src/server/api/endpoints/notes/edit.ts` * from `packages/frontend/src/components/MkNote*.vue` to @@ -628,12 +687,20 @@ seems to do a decent job) `packages/frontend/src/pages/timeline.vue`, `packages/frontend/src/ui/deck/tl-column.vue`, `packages/frontend/src/widgets/WidgetTimeline.vue`) +* if there have been any changes to the federated user data (the + `renderPerson` function in + `packages/backend/src/core/activitypub/ApRendererService.ts`), make + sure that the set of fields in `userNeedsPublishing` and + `profileNeedsPublishing` in + `packages/backend/src/server/api/endpoints/i/update.ts` are still + correct * check the changes against our `develop` (`git diff develop`) and against Misskey (`git diff misskey/develop`) * re-generate `misskey-js` (`pnpm build-misskey-js-with-types`) and commit * build the frontend: `rm -rf built/; NODE_ENV=development pnpm - --filter=frontend --filter=frontend-embed build` (the `development` - tells it to keep some of the original filenames in the built files) + --filter=frontend --filter=frontend-embed --filter=frontend-shared + build` (the `development` tells it to keep some of the original + filenames in the built files) * make sure there aren't any new `ti-*` classes (Tabler Icons), and replace them with appropriate `ph-*` ones (Phosphor Icons): `grep -rP '["'\'']ti[ -](?!fw)' -- built/` should show you what to change. @@ -641,16 +708,19 @@ seems to do a decent job) alone after every change, re-build the frontend and check again, until - there are no more `ti-*` classes in the built files + there are no more `ti-*` classes in the built files (you can ignore + the source maps) commit! * double-check the new migration, that they won't conflict with our db changes: `git diff develop -- packages/backend/migration/` * `pnpm clean; pnpm build` -* run tests `pnpm --filter='!megalodon' test` (requires a test - database, [see above](#testing)) and fix as much as you can +* run tests `pnpm --filter='!megalodon' test; pnpm --filter backend + test:e2e` (requires a test database, [see above](#testing)) and fix + as much as you can * right now `megalodon` doesn't pass its tests, so we skip them -* run lint `pnpm --filter=backend lint` + `pnpm --filter=frontend - eslint` and fix as much as you can +* run lint `pnpm --filter=backend --filter=frontend-shared lint` + + `pnpm --filter=frontend --filter=frontend-embed eslint` and fix as + much as you can Then push and open a Merge Request. diff --git a/Dockerfile b/Dockerfile index abee7fb098..8f7f8c0805 100644 --- a/Dockerfile +++ b/Dockerfile @@ -1,6 +1,6 @@ # syntax = docker/dockerfile:1.4 -ARG NODE_VERSION=20.16.0-alpine3.20 +ARG NODE_VERSION=22.11.0-alpine3.20 FROM node:${NODE_VERSION} as build diff --git a/SECURITY.md b/SECURITY.md index cfc0614dd6..8d3d44db41 100644 --- a/SECURITY.md +++ b/SECURITY.md @@ -7,3 +7,10 @@ This will allow us to assess the risk, and make a fix available before we add a bug report to the GitLab repository. Thanks for helping make Sharkey safe for everyone. + +## When create a patch + +If you can also create a patch to fix the vulnerability, please create a PR on the private fork. + +> [!note] +> There is a GitHub bug that prevents merging if a PR not following the develop branch of upstream, so please keep follow the develop branch. diff --git a/cypress/e2e/basic.cy.ts b/cypress/e2e/basic.cy.ts index d2525e0a7d..d2efbf709c 100644 --- a/cypress/e2e/basic.cy.ts +++ b/cypress/e2e/basic.cy.ts @@ -23,6 +23,7 @@ describe('Before setup instance', () => { cy.intercept('POST', '/api/admin/accounts/create').as('signup'); + cy.get('[data-cy-admin-initial-password] input').type('example_password_please_change_this_or_you_will_get_hacked'); cy.get('[data-cy-admin-username] input').type('admin'); cy.get('[data-cy-admin-password] input').type('admin1234'); cy.get('[data-cy-admin-ok]').click(); @@ -119,11 +120,16 @@ describe('After user signup', () => { it('signin', () => { cy.visitHome(); - cy.intercept('POST', '/api/signin').as('signin'); + cy.intercept('POST', '/api/signin-flow').as('signin'); cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type('alice'); - // Enterã‚ーã§ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã§ãã‚‹ã‹ã®ç¢ºèªã‚‚å…¼ãã‚‹ + + cy.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 }); + // Enterã‚ーã§ç¶šè¡Œã§ãã‚‹ã‹ã®ç¢ºèªã‚‚å…¼ãã‚‹ + cy.get('[data-cy-signin-username] input').type('alice{enter}'); + + cy.get('[data-cy-signin-page-password]').should('be.visible', { timeout: 10000 }); + // Enterã‚ーã§ç¶šè¡Œã§ãã‚‹ã‹ã®ç¢ºèªã‚‚å…¼ãã‚‹ cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); cy.wait('@signin'); @@ -138,8 +144,9 @@ describe('After user signup', () => { cy.visitHome(); cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type('alice'); - cy.get('[data-cy-signin-password] input').type('alice1234{enter}'); + + cy.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 }); + cy.get('[data-cy-signin-username] input').type('alice{enter}'); // TODO: cypressã«ãƒ–ラウザã®è¨€èªžæŒ‡å®šã§ãる機能ãŒå®Ÿè£…ã•れ次第英語ã®ã¿ãƒ†ã‚¹ãƒˆã™ã‚‹ã‚ˆã†ã«ã™ã‚‹ cy.contains(/アカウントãŒå‡çµã•れã¦ã„ã¾ã™|This account has been suspended due to/gi); diff --git a/cypress/support/commands.ts b/cypress/support/commands.ts index 281f2e6ccd..197ff963ac 100644 --- a/cypress/support/commands.ts +++ b/cypress/support/commands.ts @@ -48,16 +48,19 @@ Cypress.Commands.add('registerUser', (username, password, isAdmin = false) => { cy.request('POST', route, { username: username, password: password, + ...(isAdmin ? { setupPassword: 'example_password_please_change_this_or_you_will_get_hacked' } : {}), }).its('body').as(username); }); Cypress.Commands.add('login', (username, password) => { cy.visitHome(); - cy.intercept('POST', '/api/signin').as('signin'); + cy.intercept('POST', '/api/signin-flow').as('signin'); cy.get('[data-cy-signin]').click(); - cy.get('[data-cy-signin-username] input').type(username); + cy.get('[data-cy-signin-page-input]').should('be.visible', { timeout: 1000 }); + cy.get('[data-cy-signin-username] input').type(`${username}{enter}`); + cy.get('[data-cy-signin-page-password]').should('be.visible', { timeout: 10000 }); cy.get('[data-cy-signin-password] input').type(`${password}{enter}`); cy.wait('@signin').as('signedIn'); diff --git a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts b/idea/MkAbuseReport.stories.impl.ts index cf09c96fd4..717bceb23d 100644 --- a/packages/frontend/src/components/MkAbuseReport.stories.impl.ts +++ b/idea/MkAbuseReport.stories.impl.ts @@ -7,8 +7,8 @@ import { action } from '@storybook/addon-actions'; import { StoryObj } from '@storybook/vue3'; import { HttpResponse, http } from 'msw'; -import { abuseUserReport } from '../../.storybook/fakes.js'; -import { commonHandlers } from '../../.storybook/mocks.js'; +import { abuseUserReport } from '../packages/frontend/.storybook/fakes.js'; +import { commonHandlers } from '../packages/frontend/.storybook/mocks.js'; import MkAbuseReport from './MkAbuseReport.vue'; export const Default = { render(args) { diff --git a/idea/MkDisableSection.vue b/idea/MkDisableSection.vue index d177886569..360705071b 100644 --- a/idea/MkDisableSection.vue +++ b/idea/MkDisableSection.vue @@ -34,7 +34,7 @@ defineProps<{ width: 100%; height: 100%; cursor: not-allowed; - --color: color(from var(--error) srgb r g b / 0.25); + --color: color(from var(--MI_THEME-error) srgb r g b / 0.25); background-size: auto auto; background-image: repeating-linear-gradient(135deg, transparent, transparent 10px, var(--color) 4px, var(--color) 14px); } diff --git a/locales/ar-SA.yml b/locales/ar-SA.yml index de24ad4bb9..2f1b391b53 100644 --- a/locales/ar-SA.yml +++ b/locales/ar-SA.yml @@ -343,7 +343,6 @@ enableLocalTimeline: "ØªÙØ¹ÙŠÙ„ الخيط المØÙ„ÙŠ" enableGlobalTimeline: "ØªÙØ¹ÙŠÙ„ الخيط الزمني الشامل" disablingTimelinesInfo: "سيتمكن المديرون والمشرÙون من الوصول إلى كل الخيوط الزمنية ØØªÙ‰ وإن لم ØªÙØ¹Ù‘Ù„." registration: "إنشاء ØØ³Ø§Ø¨" -enableRegistration: "ØªÙØ¹ÙŠÙ„ إنشاء Ø§Ù„ØØ³Ø§Ø¨Ø§Øª الجديدة" invite: "دعوة" driveCapacityPerLocalAccount: "ØØµØ© التخزين لكل مستخدم Ù…ØÙ„ÙŠ" driveCapacityPerRemoteAccount: "ØØµØ© التخزين لكل مستخدم بعيد" diff --git a/locales/bn-BD.yml b/locales/bn-BD.yml index 0e761b0743..6cd577b4a9 100644 --- a/locales/bn-BD.yml +++ b/locales/bn-BD.yml @@ -339,7 +339,6 @@ enableLocalTimeline: "সà§à¦¥à¦¾à¦¨à§€à§Ÿ টাইমলাইন চালৠenableGlobalTimeline: "গà§à¦²à§‹à¦¬à¦¾à¦² টাইমলাইন চালৠকরà§à¦¨" disablingTimelinesInfo: "আপনি à¦à¦‡ টাইমলাইনগà§à¦²à¦¿ বনà§à¦§ করলেও পà§à¦°à¦¶à¦¾à¦¸à¦• à¦à¦¬à¦‚ মডারেটররা à¦à¦‡ টাইমলাইনগà§à¦²à¦¿ বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦° করতে পারবে" registration: "নিবনà§à¦§à¦¨" -enableRegistration: "নতà§à¦¨ বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦°à¦•ারী নিবনà§à¦§à¦¨ চালৠকরà§à¦¨" invite: "আমনà§à¦¤à§à¦°à¦£" driveCapacityPerLocalAccount: "পà§à¦°à¦¤à§à¦¯à§‡à¦• সà§à¦¥à¦¾à¦¨à§€à§Ÿ বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦°à¦•ারীর জনà§à¦¯ ডà§à¦°à¦¾à¦‡à¦à§‡à¦° জায়গা" driveCapacityPerRemoteAccount: "পà§à¦°à¦¤à§à¦¯à§‡à¦• রিমোট বà§à¦¯à¦¾à¦¬à¦¹à¦¾à¦°à¦•ারীর জনà§à¦¯ ডà§à¦°à¦¾à¦‡à¦à§‡à¦° জায়গা" diff --git a/locales/ca-ES.yml b/locales/ca-ES.yml index b9f3fecc76..1aca3390e6 100644 --- a/locales/ca-ES.yml +++ b/locales/ca-ES.yml @@ -2,42 +2,43 @@ _lang_: "Català " headlineMisskey: "Una xarxa connectada per notes" introMisskey: "Benvingut! Misskey és un servei de microblogging descentralitzat de codi obert.\nCrea \"notes\" per compartir els teus pensaments amb tots els que t'envolten. 📡\nAmb \"reaccions\", també pots expressar rà pidament els teus sentiments sobre les notes de tothom. ðŸ‘\nExplorem un món nou! 🚀" -poweredByMisskeyDescription: "{name} És un del serveis (anomenats instà ncies de Misskey) que utilitzen la plataforma de codi obert <b>Misskey</b>." +poweredByMisskeyDescription: "{name} És un dels serveis (anomenats instà ncies de Misskey) que utilitzen la plataforma de codi obert <b>Misskey</b>." monthAndDay: "{day}/{month}" search: "Cercar" notifications: "Notificacions" username: "Nom d'usuari" password: "Contrasenya" -initialPasswordForSetup: "Contrasenya inicial per la configuració inicial" +initialPasswordForSetup: "Contrasenya inicial per fer la primera configuració " initialPasswordIsIncorrect: "La contrasenya no és correcta." -forgotPassword: "Contrasenya oblidada" -fetchingAsApObject: "Cercant en el Fediverse..." +initialPasswordForSetupDescription: "Fes servir la contrasenya que has fet servir al fitxer de configuració, si tu mateix has instal·lat Misskey.\nSi fas servir una empresa d'allotjament de Misskey, fes servir la contrasenya que t'han donat.\nSi no has posat cap contrasenya deixar l'espai en blanc." +forgotPassword: "Restableix la contrasenya " +fetchingAsApObject: "Cercant al Fediverse..." ok: "OK" -gotIt: "Ho he entès!" +gotIt: "D'acord " cancel: "Cancel·lar" noThankYou: "No, grà cies" enterUsername: "Introdueix el teu nom d'usuari" -renotedBy: "Impulsat per {usuari}" +renotedBy: "Impulsat per {user}" noNotes: "Cap nota" noNotifications: "Cap notificació" -instance: "Servidor" +instance: "Instà ncia " settings: "Preferències" -notificationSettings: "Parà metres de notificacions" +notificationSettings: "Configurar les notificacions" basicSettings: "Configuració bà sica" -otherSettings: "Configuració avançada" -openInWindow: "Obrir en una nova finestra" +otherSettings: "Altres configuracions" +openInWindow: "Obrir en una finestra nova" profile: "Perfil" timeline: "LÃnia de temps" noAccountDescription: "Aquest usuari encara no ha escrit la seva biografia." login: "Iniciar sessió" -loggingIn: "Identificant-se" +loggingIn: "Iniciar la sessió " logout: "Tancar la sessió" signup: "Registrar-se" uploading: "Pujant..." save: "Desa" users: "Usuaris" addUser: "Afegir un usuari" -favorite: "Afegir a preferits" +favorite: "Afegeix als preferits" favorites: "Favorits" unfavorite: "Eliminar dels preferits" favorited: "Afegit als preferits." @@ -49,26 +50,26 @@ copyContent: "Copiar el contingut" copyLink: "Copiar l'enllaç" copyLinkRenote: "Copiar l'enllaç de la renota" delete: "Elimina" -deleteAndEdit: "Elimina i edita" +deleteAndEdit: "Eliminar i editar" deleteAndEditConfirm: "Segur que vols eliminar aquesta publicació i editar-la? Perdrà s totes les reaccions, impulsos i respostes." addToList: "Afegir a una llista" -addToAntenna: "Afegir a l'antena" +addToAntenna: "Afegir a una antena" sendMessage: "Enviar un missatge" copyRSS: "Copiar RSS" copyUsername: "Copiar nom d'usuari" copyUserId: "Copiar ID d'usuari" -copyNoteId: "Copiar ID de nota" -copyFileId: "Copiar ID d'arxiu" -copyFolderId: "Copiar ID de carpeta" -copyProfileUrl: "Copiar URL del perfil" +copyNoteId: "Copiar ID de la nota" +copyFileId: "Copiar ID de l'arxiu" +copyFolderId: "Copiar ID de la carpeta" +copyProfileUrl: "Copiar adreça URL del perfil" searchUser: "Cercar un usuari" -searchThisUsersNotes: "Cerca les publicacions de l'usuari" -reply: "Respondre" +searchThisUsersNotes: "Cercar les publicacions de l'usuari" +reply: "Respon" loadMore: "Carregar més" showMore: "Veure més" -showLess: "Mostra menys" +showLess: "Mostrar menys" youGotNewFollower: "t'ha seguit" -receiveFollowRequest: "Sol·licitud de seguiment rebuda" +receiveFollowRequest: "Has rebut una sol·licitud de seguiment" followRequestAccepted: "Sol·licitud de seguiment acceptada" mention: "Menció" mentions: "Mencions" @@ -77,25 +78,25 @@ importAndExport: "Importar / Exportar" import: "Importar" export: "Exporta" files: "Fitxers" -download: "Baixar" -driveFileDeleteConfirm: "Està s segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer adjunt també se suprimiran." -unfollowConfirm: "Està s segur que vols deixar de seguir {name}?" -exportRequested: "Has sol·licitat una exportació. Això pot trigar una estona. S'afegirà a la teva unitat un cop completat." -importRequested: "Has sol·licitat una importació. Això pot trigar una estona." +download: "Descarregar" +driveFileDeleteConfirm: "Està s segur que vols suprimir el fitxer \"{name}\"? Les notes associades a aquest fitxer també seran esborrades." +unfollowConfirm: "Segur que vols deixar de seguir a {name}?" +exportRequested: "Has sol·licitat una exportació de dades. Això pot trigar una estona. S'afegirà a la teva unitat de disc un cop estigui completada." +importRequested: "Has sol·licitat una importació de dades. Això pot trigar una estona." lists: "Llistes" noLists: "No tens cap llista" note: "Nota" notes: "Notes" -following: "Seguint" +following: "Segueixes " followers: "Seguidors" followsYou: "Et segueix" createList: "Crear llista" manageLists: "Gestionar les llistes" error: "Error" somethingHappened: "S'ha produït un error" -retry: "Torna-ho a intentar" +retry: "Torna-ho a provar" pageLoadError: "S'ha produït un error en carregar la pà gina" -pageLoadErrorDescription: "Això normalment es deu a errors de xarxa o a la memòria cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després d'esperar una estona." +pageLoadErrorDescription: "Això normalment és a causa d'errors a la xarxa o a la memòria cau del navegador. Prova d'esborrar la memòria cau i torna-ho a provar després d'esperar un temps." serverIsDead: "Aquest servidor no respon. Espera una estona i torna-ho a provar." youShouldUpgradeClient: "Per veure aquesta pà gina, actualitzeu-la per actualitzar el vostre client." enterListName: "Introdueix un nom per a la llista" @@ -103,52 +104,52 @@ privacy: "Privadesa" makeFollowManuallyApprove: "Les sol·licituds de seguiment requereixen aprovació" defaultNoteVisibility: "Visibilitat per defecte" follow: "Seguint" -followRequest: "Enviar la sol·licitud de seguiment" +followRequest: "Enviar sol·licitud de seguiment" followRequests: "Sol·licituds de seguiment" unfollow: "Deixar de seguir" followRequestPending: "Sol·licituds de seguiment pendents" enterEmoji: "Introduir un emoji" -renote: "Impulsa" +renote: "Impulsar " unrenote: "Anul·la l'impuls" renoted: "S'ha impulsat" renotedToX: "Impulsat per {name}." cantRenote: "No es pot impulsar aquesta publicació" -cantReRenote: "No es pot impulsar l'impuls." +cantReRenote: "No es pot impulsar un impuls." quote: "Cita" -inChannelRenote: "Renotar només al Canal" -inChannelQuote: "Citar només al Canal" -renoteToChannel: "Impulsa a un canal" -renoteToOtherChannel: "Impulsa a un altre canal" +inChannelRenote: "Impulsar només a un canal" +inChannelQuote: "Citar només a un canal" +renoteToChannel: "Impulsar a un canal" +renoteToOtherChannel: "Impulsar a un altre canal" pinnedNote: "Nota fixada" pinned: "Fixar al perfil" you: "Tu" clickToShow: "Fes clic per mostrar" -sensitive: "NSFW" +sensitive: "Sensible" add: "Afegir" -reaction: "Reaccions" +reaction: "Reacció " reactions: "Reaccions" -emojiPicker: "Selecció d'emojis" -pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb el qual reaccionar" -pinnedEmojisSettingDescription: "Selecciona l'emoji amb el qual reaccionar" -emojiPickerDisplay: "Visualitza el selector d'emojis" +emojiPicker: "Selector d'emojis" +pinnedEmojisForReactionSettingDescription: "Selecciona l'emoji amb qui vols reaccionar" +pinnedEmojisSettingDescription: "Selecciona quins emojis vols deixar fixats i es mostrin en obrir el selector d'emojis" +emojiPickerDisplay: "Mostrar el selector d'emojis" overwriteFromPinnedEmojisForReaction: "Reemplaça els emojis de la reacció" -overwriteFromPinnedEmojis: "Sobreescriu des dels emojis fixats" +overwriteFromPinnedEmojis: "Sobreescriu els emojis fixats al panel de reaccions" reactionSettingDescription2: "Arrossega per reordenar, fes clic per suprimir, prem \"+\" per afegir." rememberNoteVisibility: "Recorda la configuració de visibilitat de les notes" attachCancel: "Eliminar el fitxer adjunt" deleteFile: "Esborrar l'arxiu " -markAsSensitive: "Marcar com a NSFW" +markAsSensitive: "Marcar com a sensible" unmarkAsSensitive: "Deixar de marcar com a sensible" enterFileName: "Defineix nom del fitxer" mute: "Silencia" unmute: "Deixa de silenciar" -renoteMute: "Silenciar Renotes" -renoteUnmute: "Treure el silenci de les renotes" +renoteMute: "Silenciar impulsos" +renoteUnmute: "Treure el silenci dels impulsos" block: "Bloqueja" unblock: "Desbloqueja" suspend: "Suspèn" unsuspend: "Deixa de suspendre" -blockConfirm: "Vols bloquejar?" +blockConfirm: "Vols bloquejar-lo?" unblockConfirm: "Vols desbloquejar-lo?" suspendConfirm: "Està s segur que vols suspendre aquest compte?" unsuspendConfirm: "Està s segur que vols treure la suspensió d'aquest compte?" @@ -174,11 +175,11 @@ youCanCleanRemoteFilesCache: "Pots netejar la memòria cau fent clic al botó de cacheRemoteSensitiveFiles: "Posar a la memòria cau arxius remots sensibles" cacheRemoteSensitiveFilesDescription: "Quan aquesta opció és desactiva, els arxius remots sensibles es carregant directament del servidor d'origen sense que es guardin a la memòria cau." flagAsBot: "Marca aquest compte com a bot" -flagAsBotDescription: "Marca aquest compte com a bot" +flagAsBotDescription: "Activa aquesta opció si el compte el controla un programa. Si s'activa, actuarà com un senyal per altres desenvolupadors per prevenir cadenes d'interacció sense fi i ajustar els parà metres interns de Misskey pe tractar el compte com un bot." flagAsCat: "Marca aquest compte com a gat" flagAsCatDescription: "Activeu aquesta opció per marcar aquest compte com a gat." flagShowTimelineReplies: "Mostra les respostes a la lÃnia de temps" -flagShowTimelineRepliesDescription: "Mostra les respostes a la lÃnia de temps" +flagShowTimelineRepliesDescription: "Mostra les respostes dels usuaris a les notes d'altres usuaris a la lÃnia de temps." autoAcceptFollowed: "Aprova automà ticament les sol·licituds de seguiment dels usuaris que segueixes" addAccount: "Afegeix un compte" reloadAccountsList: "Recarregar la llista de contactes" @@ -203,7 +204,7 @@ selectUser: "Selecciona usuari/a" recipient: "Destinatari" annotation: "Comentaris" federation: "Federació" -instances: "Servidors" +instances: "Instà ncies " registeredAt: "Registrat a" latestRequestReceivedAt: "Última petició rebuda" latestStatus: "Últim estat" @@ -212,7 +213,7 @@ charts: "Grà fics" perHour: "Per hora" perDay: "Per dia" stopActivityDelivery: "Deixa d'enviar activitats" -blockThisInstance: "Deixa d'enviar activitats" +blockThisInstance: "Bloca aquesta instà ncia " silenceThisInstance: "Silencia aquesta instà ncia " mediaSilenceThisInstance: "Silenciar els arxius d'aquesta instà ncia " operations: "Accions" @@ -227,7 +228,7 @@ network: "Xarxa" disk: "Disc" instanceInfo: "Informació del fitxer d'instal·lació" statistics: "EstadÃstiques" -clearQueue: "Esborrar la cua" +clearQueue: "Esborra la cua de feina" clearQueueConfirmTitle: "Esteu segur que voleu esborrar la cua?" clearQueueConfirmText: "Les notes no lliurades que quedin a la cua no es federaran. Normalment aquesta operació no és necessà ria." clearCachedFiles: "Esborra la memòria cau" @@ -253,7 +254,7 @@ processing: "S'està processant..." preview: "Vista prèvia" default: "Per defecte" defaultValueIs: "Per defecte: {value}" -noCustomEmojis: "Cap emoji personalitzat" +noCustomEmojis: "No hi ha emojis personalitzats" noJobs: "No hi ha feines" federating: "Federant" blocked: "Bloquejat" @@ -267,11 +268,11 @@ instanceFollowers: "Seguidors del servidor" instanceUsers: "Usuaris del servidor" changePassword: "Canvia la contrasenya" security: "Seguretat" -retypedNotMatch: "L'entrada no coincideix" +retypedNotMatch: "Les entrades no coincideix" currentPassword: "Contrasenya actual" newPassword: "Contrasenya nova" -newPasswordRetype: "Contrasenya nou (repeteix-la)" -attachFile: "Adjunta fitxers" +newPasswordRetype: "Contrasenya nova (repeteix-la)" +attachFile: "Afegeix un arxiu" more: "Més" featured: "Destacat" usernameOrUserId: "Nom o ID d'usuari" @@ -281,25 +282,25 @@ announcements: "Anuncis" imageUrl: "URL de la imatge" remove: "Eliminar" removed: "Eliminat" -removeAreYouSure: "Segur que voleu retirar «{x}»?" -deleteAreYouSure: "Segur que voleu retirar «{x}»?" -resetAreYouSure: "Segur que voleu restablir-ho?" -areYouSure: "Està segur?" +removeAreYouSure: "Segur que vols esborrar «{x}»?" +deleteAreYouSure: "Segur que vols esborrar «{x}»?" +resetAreYouSure: "Segur que vols restablir-ho?" +areYouSure: "Està s segur?" saved: "S'ha desat" messaging: "Xat" upload: "Puja" keepOriginalUploading: "Guarda la imatge original" -keepOriginalUploadingDescription: "Guarda la imatge pujada com hi és. Si està apagat, una versió per a la visualització a la xarxa serà generada quan sigui pujada." -fromDrive: "Des de la unitat" +keepOriginalUploadingDescription: "Guarda la imatge pujada sense modificar. Si està desactivat, es generarà una versió per visualitzar a la web en pujar la imatge." +fromDrive: "Des del Disc" fromUrl: "Des d'un enllaç" uploadFromUrl: "Carrega des d'un enllaç" uploadFromUrlDescription: "Enllaç del fitxer que vols carregar" uploadFromUrlRequested: "Cà rrega sol·licitada" -uploadFromUrlMayTakeTime: "La cà rrega des de l'enllaç pot prendre un temps" +uploadFromUrlMayTakeTime: "La cà rrega des de l'enllaç pot trigar un temps" explore: "Explora" messageRead: "Vist" -noMoreHistory: "No hi resta més per veure" -startMessaging: "Començar a xatejar" +noMoreHistory: "No hi ha res més per veure" +startMessaging: "Comença a xatejar" nUsersRead: "Vist per {n}" agreeTo: "Accepto que {0}" agree: "Hi estic d'acord" @@ -311,7 +312,7 @@ home: "Inici" remoteUserCaution: "Ja que aquest usuari resideix a una instà ncia remota, la informació mostrada es podria trobar incompleta." activity: "Activitat" images: "Imatges" -image: "Imatges" +image: "Imatge" birthday: "Aniversari" yearsOld: "{age} anys" registeredDate: "Data de registre" @@ -326,10 +327,10 @@ darkThemes: "Temes foscos" syncDeviceDarkMode: "Sincronitza el mode fosc amb la configuració del dispositiu" drive: "Unitat" fileName: "Nom del Fitxer" -selectFile: "Selecciona fitxers" +selectFile: "Selecciona un fitxer" selectFiles: "Selecciona fitxers" selectFolder: "Selecció de carpeta" -selectFolders: "Selecció de carpeta" +selectFolders: "Selecció de carpetes" fileNotSelected: "Cap fitxer seleccionat" renameFile: "Canvia el nom del fitxer" folderName: "Nom de la carpeta" @@ -358,9 +359,9 @@ reload: "Actualitza" doNothing: "Ignora" reloadConfirm: "Vols recarregar?" watch: "Veure" -unwatch: "Deixar de veure" +unwatch: "Deixa de veure" accept: "Acceptar" -reject: "Denegar" +reject: "Denega" normal: "Normal" instanceName: "Nom del servidor" instanceDescription: "Descripció del servidor" @@ -381,7 +382,6 @@ enableLocalTimeline: "Activa la lÃnia de temps local" enableGlobalTimeline: "Activa la lÃnia de temps global" disablingTimelinesInfo: "Fins i tot si aquestes lÃnies de temps són desactivades, els administradors i els moderadors poden continuar visualitzant per conveniència." registration: "Registre" -enableRegistration: "Permet els registres d'usuaris" invite: "Convida" driveCapacityPerLocalAccount: "Capacitat del disc per usuaris locals" driveCapacityPerRemoteAccount: "Capacitat del disc per usuaris remots" @@ -392,20 +392,20 @@ basicInfo: "Informació bà sica" pinnedUsers: "Usuaris fixats" pinnedUsersDescription: "Llista d'usuaris, separats per salts de lÃnia, que seran fixats a la pestanya \"Explorar\"." pinnedPages: "Pà gines fixades" -pinnedPagesDescription: "Escriu els camins de les pà gines que vols fixar a la pà gina d'inici d'aquesta instà ncia. Separades per salts de lÃnia." +pinnedPagesDescription: "Escriu les adreces de les pà gines que vols fixar a la pà gina d'inici d'aquesta instà ncia. Separades per salts de lÃnia." pinnedClipId: "ID del retall fixat" pinnedNotes: "Nota fixada" hcaptcha: "hCaptcha" -enableHcaptcha: "Activar hCaptcha" +enableHcaptcha: "Activa hCaptcha" hcaptchaSiteKey: "Clau del lloc" hcaptchaSecretKey: "Clau secreta" mcaptcha: "mCaptcha" -enableMcaptcha: "Activar mCaptcha" +enableMcaptcha: "Activa mCaptcha" mcaptchaSiteKey: "Clau del lloc" mcaptchaSecretKey: "Clau secreta" mcaptchaInstanceUrl: "Adreça URL del servidor mCaptcha" recaptcha: "reCAPTCHA" -enableRecaptcha: "Activar reCAPTCHA" +enableRecaptcha: "Activa reCAPTCHA" recaptchaSiteKey: "Clau del lloc" recaptchaSecretKey: "Clau secreta" turnstile: "Turnstile" @@ -447,14 +447,14 @@ aboutMisskey: "Quant a Misskey" administrator: "Administrador/a" token: "Codi de verificació" 2fa: "Autenticació de doble factor" -setupOf2fa: "Configurar l'autenticació de doble factor" +setupOf2fa: "Configura l'autenticació de doble factor" totp: "Aplicació d'autenticació" totpDescription: "Escriu una contrasenya d'un sol us fent servir l'aplicació d'autenticació" moderator: "Moderador/a" moderation: "Moderació" moderationNote: "Nota de moderació " moderationNoteDescription: "Pots escriure notes que es compartiran entre els moderadors." -addModerationNote: "Afegir una nota de moderació " +addModerationNote: "Afegeix una nota de moderació " moderationLogs: "Registre de moderació " nUsersMentioned: "{n} usuaris mencionats" securityKeyAndPasskey: "Clau de seguretat / Clau de pas" @@ -470,13 +470,13 @@ reduceUiAnimation: "Redueix les animacions de la interfÃcie" share: "Comparteix" notFound: "No s'ha trobat" notFoundDescription: "No es troba cap pà gina que correspongui a aquesta adreça" -uploadFolder: "Carpeta per defecte per pujades" +uploadFolder: "Carpeta per defecte on desar els arxius pujats" markAsReadAllNotifications: "Marca totes les notificacions com a llegides" markAsReadAllUnreadNotes: "Marca-ho tot com a llegit" markAsReadAllTalkMessages: "Marcar tots els missatges com llegits" help: "Ajuda" inputMessageHere: "Escriu aquà el teu missatge " -close: "Tancar" +close: "Tanca" invites: "Convida" members: "Membres" transfer: "Transferir" @@ -507,7 +507,7 @@ normalPassword: "Bona contrasenya" strongPassword: "Contrasenya segura" passwordMatched: "Correcte!" passwordNotMatched: "No coincideix" -signinWith: "Inicia sessió amb amb {x}" +signinWith: "Inicia sessió amb {x}" signinFailed: "Autenticació sense èxit. Intenta-ho un altre cop utilitzant la contrasenya i el nom correctes." or: "O" language: "Idioma" @@ -586,11 +586,12 @@ masterVolume: "Volum principal" notUseSound: "Sense so" useSoundOnlyWhenActive: "Reproduir sons només quan Misskey estigui actiu" details: "Detalls" +renoteDetails: "Més informació sobre l'impuls " chooseEmoji: "Tria un emoji" unableToProcess: "L'operació no pot ser completada " recentUsed: "Utilitzat recentment" install: "Instal·lació " -uninstall: "Desinstal·lar " +uninstall: "Desinstal·la" installedApps: "Aplicacions autoritzades " nothing: "No hi ha res per veure aquà " installedDate: "Data d'instal·lació" @@ -607,13 +608,13 @@ output: "Sortida" script: "Script" disablePagesScript: "Desactivar AiScript a les pà gines " updateRemoteUser: "Actualitzar la informació de l'usuari remot" -unsetUserAvatar: "Desactivar l'avatar " +unsetUserAvatar: "Desactiva l'avatar " unsetUserAvatarConfirm: "Segur que vols desactivar l'avatar?" -unsetUserBanner: "Desactivar el bà ner " +unsetUserBanner: "Desactiva el bà ner " unsetUserBannerConfirm: "Segur que vols desactivar el bà ner?" -deleteAllFiles: "Esborrar tots els arxius" +deleteAllFiles: "Esborra tots els arxius" deleteAllFilesConfirm: "Segur que vols esborrar tots els arxius?" -removeAllFollowing: "Deixar de seguir tots els usuaris seguits" +removeAllFollowing: "Deixa de seguir tots els usuaris seguits" removeAllFollowingDescription: "El fet d'executar això, et farà deixar de seguir a tots els usuaris de {host}. Si us plau, executa això si l'amfitrió, per exemple, ja no existeix." userSuspended: "Aquest usuari ha sigut suspès" userSilenced: "Aquest usuari està sent silenciat" @@ -946,6 +947,9 @@ oneHour: "1 hora" oneDay: "Un dia" oneWeek: "Una setmana" oneMonth: "Un mes" +threeMonths: "3 mesos" +oneYear: "1 any" +threeDays: "3 dies" reflectMayTakeTime: "Això pot trigar una estona a tenir efecte" failedToFetchAccountInformation: "No es pot obtenir la informació del compte" rateLimitExceeded: "S'ha arribat al mà xim de peticions" @@ -1086,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Tornar a intentar-ho tot?" retryAllQueuesConfirmText: "Això farà que la cà rrega del servidor augmenti temporalment." enableChartsForRemoteUser: "Generar grà fiques d'usuaris remots" enableChartsForFederatedInstances: "Generar grà fiques d'instà ncies remotes" +enableStatsForFederatedInstances: "Activa les estadÃstiques de les instà ncies remotes federades" showClipButtonInNoteFooter: "Afegir \"Retall\" al menú d'acció de la nota" reactionsDisplaySize: "Mida de les reaccions" limitWidthOfReaction: "Limitar l'amplada mà xima de la reacció i mostrar-les en una mida reduïda " @@ -1178,8 +1183,8 @@ currentAnnouncements: "Informes actuals" pastAnnouncements: "Informes passats" youHaveUnreadAnnouncements: "Tens informes per llegir." useSecurityKey: "Segueix les instruccions del teu navegador O dispositiu per fer servir el teu passkey." -replies: "Respondre" -renotes: "Impulsa" +replies: "Respon" +renotes: "Impulsar " loadReplies: "Mostrar les respostes" loadConversation: "Mostrar la conversació " pinnedList: "Llista fixada" @@ -1287,6 +1292,27 @@ passkeyVerificationSucceededButPasswordlessLoginDisabled: "La verificació de la messageToFollower: "Missatge als meus seguidors" target: "Assumpte " testCaptchaWarning: "És una caracterÃstica dissenyada per a la prova de CAPTCHA. <strong>No l'utilitzes en l'entorn real.</strong>" +prohibitedWordsForNameOfUser: "Noms prohibits per escollir noms d'usuari " +prohibitedWordsForNameOfUserDescription: "Si qualsevol d'aquestes paraules es troben a un nom d'usuari la creació de l'usuari no es durà a terme. Als moderadors no els afecta aquesta restricció." +yourNameContainsProhibitedWords: "El nom conté paraules prohibides " +yourNameContainsProhibitedWordsDescription: "Si de veritat vols fer servir aquest nom posat en contacte amb l'administrador." +thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autor requereix l'inici de sessió per poder veure" +lockdown: "Bloquejat" +pleaseSelectAccount: "Seleccionar un compte" +availableRoles: "Roles disponibles " +acknowledgeNotesAndEnable: "Activa'l després de comprendre els possibles perills." +_accountSettings: + requireSigninToViewContents: "És obligatori l'inici de sessió per poder veure el contingut" + requireSigninToViewContentsDescription1: "Es requereix l'inici de sessió per poder veure totes les notes i el contingut que has creat. Amb això esperem evitar que els rastrejadors recopilin informació." + requireSigninToViewContentsDescription2: "També es desactivaran les vistes prèvies d'URLS (OGP), la incrustació a pà gines web i la visualització des de servidors que no admetin la citació de notes." + requireSigninToViewContentsDescription3: "Aquestes restriccions pot ser que no s'apliquin als continguts federats en servidors remots." + makeNotesFollowersOnlyBefore: "Permetre que les notes antigues només es mostrin als seguidors." + makeNotesFollowersOnlyBeforeDescription: "Mentre aquesta funció estigui activada, les notes que hagin passat la data i hora fixada o hagi passat els temps establert seran visibles només per als teus seguidors. Quan es desactivi, també es restableix l'estat públic de la nota." + makeNotesHiddenBefore: "Fes que les notes antigues siguin privades" + makeNotesHiddenBeforeDescription: "Mentres aquesta funció estigui activada les notes que hagin superat una data i hora fixada o hagi passat el temps establert només seran visibles per a tu. Si la desactives es restablirà també l'estat públic de les notes." + mayNotEffectForFederatedNotes: "Això pot ser que no afecti les notes federades." + notesHavePassedSpecifiedPeriod: "Notes publicades durant un perÃode de temps especificat." + notesOlderThanSpecifiedDateAndTime: "Notes més antigues de la data i temps especificat " _abuseUserReport: forward: "Reenviar " forwardDescription: "Reenvia l'informe a una altra instà ncia com un compte del sistema anònima." @@ -1431,6 +1457,8 @@ _serverSettings: reactionsBufferingDescription: "Quan s'activa aquesta opció millora bastant el rendiment en recuperar les lÃnies de temps reduint la cà rrega de la base. Com a contrapunt, augmentarà l'ús de memòria de RedÃs. Desactiva aquesta opció en cas de tenir un servidor amb poca memòria o si tens problemes d'inestabilitat." inquiryUrl: "URL de consulta " inquiryUrlDescription: "Escriu adreça URL per al formulari de consulta per al mantenidor del servidor o una pà gina web amb el contacte d'informació." + openRegistration: "Registres oberts" + openRegistrationWarning: "Obrir els registres és arriscat. Es recomana obrir-los només si el servidor és monitorat constantment i per respondre immediatament davant qualsevol problema." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Si no es detecta activitat per part del moderador durant un perÃode de temps, aquesta opció es desactiva automà ticament per evitar el correu brossa." _accountMigration: moveFrom: "Migrar un altre compte a aquest" @@ -2151,8 +2179,11 @@ _auth: permissionAsk: "Aquesta aplicació demana els següents permisos" pleaseGoBack: "Si us plau, torna a l'aplicació" callback: "Tornant a l'aplicació" + accepted: "Accés garantit" denied: "Accés denegat" + scopeUser: "Opera com si fossis aquest usuari" pleaseLogin: "Si us plau, identificat per autoritzar l'aplicació." + byClickingYouWillBeRedirectedToThisUrl: "Si es garanteix l'accés, serà s redirigit automà ticament a la següent adreça URL" _antennaSources: all: "Totes les publicacions" homeTimeline: "Publicacions dels usuaris seguits" @@ -2402,7 +2433,8 @@ _notification: renotedBySomeUsers: "L'han impulsat {n} usuaris" followedBySomeUsers: "Et segueixen {n} usuaris" flushNotification: "Netejar notificacions" - exportOfXCompleted: "Completada l'exportació de {n}" + exportOfXCompleted: "Completada l'exportació de {x}" + login: "Algú ha iniciat sessió " _types: all: "Tots" note: "Notes noves" @@ -2485,6 +2517,8 @@ _webhookSettings: abuseReport: "Quan reps un nou informe de moderació " abuseReportResolved: "Quan resols un informe de moderació " userCreated: "Quan es crea un usuari" + inactiveModeratorsWarning: "Quan el compte d'un moderador no té activitat durant un temps" + inactiveModeratorsInvitationOnlyChanged: "Quan el compte d'un moderador no té activitat durant un temps, i el servidor es canvia a registre per invitacions" deleteConfirm: "Segur que vols esborrar el webhook?" testRemarks: "Si feu clic al botó a la dreta de l'interruptor, podeu enviar un webhook de prova amb dades dummy." _abuseReport: @@ -2612,8 +2646,81 @@ _dataSaver: description: "Les imatges en miniatura que serveixen com a vista prèvia de les URLs no es tornaran a carregar." _code: title: "Ressaltat del codi " + description: "Quan s'utilitza codi MFM, no es llegeix fins que es copiï. En els punts destacats del codi s'han de llegir els fitxers definits per a cada llengua que resulti alt, però no es poden llegir automà ticament, per la qual cosa es poden reduir les quantitats de comunicació." +_hemisphere: + N: "Hemisferi Nord " + S: "Hemisferi Sud" + caption: "El fan servir alguns clients per determinar l'estació de l'any." _reversi: + reversi: "Reversi" + gameSettings: "Opcions del joc" + chooseBoard: "Escull un taulell" + blackOrWhite: "Negres/Blanques" + blackIs: "{name} juga amb negres " + rules: "Regles" + thisGameIsStartedSoon: "El joc començarà en breu" + waitingForOther: "Esperant la tirada de l'oponent " + waitingForMe: "Esperant el teu torn" + waitingBoth: "Prepara't " + ready: "Preparat " + cancelReady: " No preparat " + opponentTurn: "Torn de l'oponent " + myTurn: "El teu torn" + turnOf: "Li toca a {name}" + pastTurnOf: "Torn de {name}" + surrender: "Rendeix-te" + surrendered: "T'has rendit" + timeout: "Temps esgotat" + drawn: "Empat" + won: "{name} ha guanyat" + black: "Negres" + white: "Blanques" total: "Total" + turnCount: "Torn {count}" + myGames: "Jugades" + allGames: "Totes les jugades" + ended: "Acabat" + playing: "Jugant" + isLlotheo: "Qui tingui menys pedres guanya (Llotheo)" + loopedMap: "Mapa de recursiu" + canPutEverywhere: "Les fitxes es poden posar a qualsevol lloc" + timeLimitForEachTurn: "Temps lÃmit per jugada" + freeMatch: "Partida lliure" + lookingForPlayer: "Buscant contrincant..." + gameCanceled: "La partida s'ha cancel·lat " + shareToTlTheGameWhenStart: "Compartir la partida a la lÃnia de temps quan comenci" + iStartedAGame: "La partida ha començat! #MisskeyReversi" + opponentHasSettingsChanged: "L'oponent h canviat la seva configuració " + allowIrregularRules: "Regles irregulars (totalment lliure)" + disallowIrregularRules: "Sense regles irregulars" + showBoardLabels: "Mostrar el número de lÃnia i columna al tauler de joc" + useAvatarAsStone: "Fer servir els avatars dels usuaris com a fitxes" +_offlineScreen: + title: "Fora de lÃnia - No es pot connectar amb el servidor" + header: "Impossible connectar amb el servidor" +_urlPreviewSetting: + title: "Configuració per a la previsualització de l'URL" + enable: "Activa la previsualització de l'URL" + timeout: "Temps mà xim per carregar la previsualització de l'URL (ms)" + timeoutDescription: "Si l'obtenció de la previsualització triga més que el temps establert, no es generarà la vista prèvia." + maximumContentLength: "Longitud mà xima del contingut (bytes)" + maximumContentLengthDescription: "Si la mà xima longitud és més gran que aquest valor, la previsualització no es generarà ." + requireContentLength: "Generar la previsualització només si es pot obtenir la longitud mà xima " + requireContentLengthDescription: "Si l'altre servidor no proporciona la longitud mà xima, la previsualització no es generarà ." + userAgent: "User-Agent" + userAgentDescription: "Estableix l'User-Agent que és farà servir per a la recuperació de la vista prèvia. Si és deixa en blanc es farà servir l'User-Agent per defecte." + summaryProxy: "Proxy endpoints per generar vistes prèvies" + summaryProxyDescription: "La vista prèvia es genera fent servir Summaly proxy, no la genera el mateix Misskey." + summaryProxyDescription2: "Els següents parà metres són passats al proxy com cadenes de consulta. Si el proxy no els admet, s'ignoren els valors configurats." +_mediaControls: + pip: "Imatge sobre impressionada " + playbackRate: "Velocitat de reproducció " + loop: "Reproducció en bucle" +_contextMenu: + title: "Menú contextual" + app: "Aplicació " + appWithShift: "Aplicació amb la tecla shift" + native: "InterfÃcie del navegador" _embedCodeGen: title: "Personalitza el codi per incrustar" header: "Mostrar la capçalera" @@ -2628,3 +2735,12 @@ _embedCodeGen: generateCode: "Crea el codi per incrustar" codeGenerated: "Codi generat" codeGeneratedDescription: "Si us plau, enganxeu el codi generat al lloc web." +_selfXssPrevention: + warning: "Advertència " + title: "\"Enganxa qualsevol cosa en aquesta finestra\" És tot un engany." + description1: "Si posa alguna cosa al seu compte, un usuari malintencionat podria segrestar-la o robar-li les dades." + description2: "Si no entens que està s fent %cpara ara mateix i tanca la finestra." + description3: "Per obtenir més informació. {link}" +_followRequest: + recieved: "Sol·licituds rebudes" + sent: "Sol·licituds enviades" diff --git a/locales/cs-CZ.yml b/locales/cs-CZ.yml index caf6d6e163..504ba1f8c8 100644 --- a/locales/cs-CZ.yml +++ b/locales/cs-CZ.yml @@ -348,7 +348,6 @@ enableLocalTimeline: "Povolit lokálnà Äas" enableGlobalTimeline: "Povolit globálnà Äas" disablingTimelinesInfo: "AdministrátoÅ™i a ModerátoÅ™i budou mÃt stálý pÅ™Ãstup ke vÅ¡em Äasovým osám i pÅ™es to že nejsou zapnuté." registration: "Registrace" -enableRegistration: "Povolit registraci novým uživatelům" invite: "Pozvat" driveCapacityPerLocalAccount: "Kapacita disku na lokálnÃho uživatele" driveCapacityPerRemoteAccount: "Kapacita disku na vzdáleného uživatele" diff --git a/locales/de-DE.yml b/locales/de-DE.yml index 4e2bd06934..d85c930b73 100644 --- a/locales/de-DE.yml +++ b/locales/de-DE.yml @@ -8,6 +8,9 @@ search: "Suchen" notifications: "Benachrichtigungen" username: "Benutzername" password: "Passwort" +initialPasswordForSetup: "Initiales Passwort für die Einrichtung" +initialPasswordIsIncorrect: "Das initiale Passwort für die Einrichtung ist falsch" +initialPasswordForSetupDescription: "Verwende das in der Konfigurationsdatei angegebene Passwort, wenn du Misskey selbst installiert hast.\nWenn du einen Misskey-Hostingdienst o.ä. nutzt, verwende das dort angegebene Kennwort.\nWenn du kein Passwort festgelegt hast, lasse es leer, um fortzufahren." forgotPassword: "Passwort vergessen" fetchingAsApObject: "Wird aus dem Fediverse angefragt …" ok: "OK" @@ -60,6 +63,7 @@ copyFileId: "Datei-ID kopieren" copyFolderId: "Ordner-ID kopieren" copyProfileUrl: "Profil-URL kopieren" searchUser: "Nach einem Benutzer suchen" +searchThisUsersNotes: "Notizen dieses Benutzers suchen" reply: "Antworten" loadMore: "Mehr laden" showMore: "Mehr anzeigen" @@ -108,11 +112,14 @@ enterEmoji: "Gib ein Emoji ein" renote: "Renote" unrenote: "Renote zurücknehmen" renoted: "Renote getätigt." +renotedToX: "Renoted zu {name}." cantRenote: "Renote dieses Beitrags nicht möglich." cantReRenote: "Renote einer Renote nicht möglich." quote: "Zitieren" inChannelRenote: "Kanal-interner Renote" inChannelQuote: "Kanal-internes Zitat" +renoteToChannel: "Renote zu Kanal" +renoteToOtherChannel: "Renote zu anderem Kanal" pinnedNote: "Angeheftete Notiz" pinned: "Angeheftet" you: "Du" @@ -124,12 +131,13 @@ reactions: "Reaktionen" emojiPicker: "Emoji auswählen" pinnedEmojisForReactionSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie beim Reagieren als Erstes anzuzeigen." pinnedEmojisSettingDescription: "Lege Emojis fest, die angepinnt werden sollen, um sie in der Emoji-Auswahl als Erstes anzuzeigen" +emojiPickerDisplay: "Anzeige der Emoji-Auswahl" overwriteFromPinnedEmojisForReaction: "Überschreiben mit den Reaktions-Einstellungen" overwriteFromPinnedEmojis: "Überschreiben mit den allgemeinen Einstellungen" reactionSettingDescription2: "Ziehe um Anzuordnen, klicke um zu löschen, drücke „+“ um hinzuzufügen" rememberNoteVisibility: "Notizsichtbarkeit merken" attachCancel: "Anhang entfernen" -deleteFile: "Datei gelöscht" +deleteFile: "Datei löschen" markAsSensitive: "Als sensibel markieren" unmarkAsSensitive: "Als nicht sensibel markieren" enterFileName: "Dateinamen eingeben" @@ -150,6 +158,7 @@ editList: "Liste bearbeiten" selectChannel: "Kanal auswählen" selectAntenna: "Antenne auswählen" editAntenna: "Antenne bearbeiten" +createAntenna: "Erstelle eine Antenne" selectWidget: "Widget auswählen" editWidgets: "Widgets bearbeiten" editWidgetsExit: "Fertig" @@ -176,6 +185,8 @@ addAccount: "Benutzerkonto hinzufügen" reloadAccountsList: "Benutzerkontoliste aktualisieren" loginFailed: "Anmeldung fehlgeschlagen" showOnRemote: "Auf Ursprungsinstanz ansehen" +chooseServerOnMisskeyHub: "Wähle einen Server aus dem Misskey Hub" +inputHostName: "Gib die Domain an" general: "Allgemein" wallpaper: "Hintergrund" setWallpaper: "Hintergrund festlegen" @@ -186,6 +197,7 @@ followConfirm: "Möchtest du {name} wirklich folgen?" proxyAccount: "Proxy-Benutzerkonto" proxyAccountDescription: "Ein Proxy-Konto ist ein Benutzerkonto, das unter bestimmten Bedingungen als Follower für Benutzer fremder Instanzen fungiert. Wenn zum Beispiel ein Benutzer einen Benutzer einer fremden Instanz zu einer Liste hinzufügt, werden die Aktivitäten des entfernten Benutzers nicht an die Instanz übermittelt, wenn kein lokaler Benutzer diesem Benutzer folgt; stattdessen folgt das Proxy-Konto." host: "Hostname" +selectSelf: "Mich auswählen" selectUser: "Benutzer auswählen" recipient: "Empfänger" annotation: "Anmerkung" @@ -201,6 +213,7 @@ perDay: "Pro Tag" stopActivityDelivery: "Senden von Aktivitäten einstellen" blockThisInstance: "Diese Instanz blockieren" silenceThisInstance: "Instanz stummschalten" +mediaSilenceThisInstance: "Medien dieses Servers stummschalten" operations: "Aktionen" software: "Software" version: "Version" @@ -222,6 +235,8 @@ blockedInstances: "Blockierte Instanzen" blockedInstancesDescription: "Gib die Hostnamen der Instanzen, welche blockiert werden sollen, durch Zeilenumbrüche getrennt an. Blockierte Instanzen können mit dieser instanz nicht mehr kommunizieren." silencedInstances: "Stummgeschaltete Instanzen" silencedInstancesDescription: "Gib die Hostnamen der Instanzen, welche stummgeschaltet werden sollen, durch Zeilenumbrüche getrennt an. Alle Konten dieser Instanzen werden als stummgeschaltet behandelt, können nur noch Follow-Anfragen stellen und wenn nicht gefolgt keine lokalen Konten erwähnen. Blockierte Instanzen sind davon nicht betroffen." +mediaSilencedInstances: "Medien-stummgeschaltete Server" +mediaSilencedInstancesDescription: "Gib pro Zeile die Hostnamen der Server ein, dessen Medien du stummschalten möchtest. Alle Benutzerkonten der aufgeführten Server werden als sensibel behandelt und können keine benutzerdefinierten Emojis verwenden. Gesperrte Server sind davon nicht betroffen." muteAndBlock: "Stummschaltungen und Blockierungen" mutedUsers: "Stummgeschaltete Benutzer" blockedUsers: "Blockierte Benutzer" @@ -312,6 +327,7 @@ selectFile: "Datei auswählen" selectFiles: "Dateien auswählen" selectFolder: "Ordner auswählen" selectFolders: "Ordner auswählen" +fileNotSelected: "Keine Datei ausgewählt" renameFile: "Datei umbenennen" folderName: "Ordnername" createFolder: "Ordner erstellen" @@ -319,6 +335,7 @@ renameFolder: "Ordner umbenennen" deleteFolder: "Ordner löschen" folder: "Ordner" addFile: "Datei hinzufügen" +showFile: "Datei anzeigen" emptyDrive: "Deine Drive ist leer" emptyFolder: "Dieser Ordner ist leer" unableToDelete: "Nicht löschbar" @@ -361,7 +378,6 @@ enableLocalTimeline: "Lokale Chronik aktivieren" enableGlobalTimeline: "Globale Chronik aktivieren" disablingTimelinesInfo: "Administratoren und Moderatoren haben immer Zugriff auf alle Chroniken, auch wenn diese deaktiviert sind." registration: "Registrieren" -enableRegistration: "Registrierung neuer Benutzer erlauben" invite: "Einladen" driveCapacityPerLocalAccount: "Drive-Kapazität pro lokalem Benutzerkonto" driveCapacityPerRemoteAccount: "Drive-Kapazität pro Benutzer fremder Instanzen" @@ -399,6 +415,7 @@ name: "Name" antennaSource: "Antennenquelle" antennaKeywords: "Zu beobachtende Schlüsselwörter" antennaExcludeKeywords: "Zu ignorierende Schlüsselwörter" +antennaExcludeBots: "Bot-Accounts ausschließen" antennaKeywordsDescription: "Zum Nutzen einer \"UND\"-Verknüpfung Einträge mit Leerzeichen trennen, zum Nutzen einer \"ODER\"-Verknüpfung Einträge mit einem Zeilenumbruch trennen" notifyAntenna: "Über neue Notizen benachrichtigen" withFileAntenna: "Nur Notizen mit Dateien" @@ -466,6 +483,7 @@ retype: "Erneut eingeben" noteOf: "Notiz von {user}" quoteAttached: "Zitat" quoteQuestion: "Als Zitat anhängen?" +attachAsFileQuestion: "Der Text in der Zwischenablage ist lang. Möchtest du ihn als Textdatei anhängen?" noMessagesYet: "Noch keine Nachrichten vorhanden" newMessageExists: "Du hast eine neue Nachricht" onlyOneFileCanBeAttached: "Es kann pro Nachricht nur eine Datei angehängt werden" @@ -491,7 +509,11 @@ uiLanguage: "Sprache der Benutzeroberfläche" aboutX: "Über {x}" emojiStyle: "Emoji-Stil" native: "Nativ" +menuStyle: "Menü Stil" +style: "Stil" +popup: "Pop-up" showNoteActionsOnlyHover: "Notizmenü nur bei Mouseover anzeigen" +showReactionsCount: "Zeige die Anzahl der Reaktionen auf Notizen an" noHistory: "Kein Verlauf gefunden" signinHistory: "Anmeldungsverlauf" enableAdvancedMfm: "Erweitertes MFM aktivieren" @@ -572,6 +594,7 @@ ascendingOrder: "Aufsteigende Reihenfolge" descendingOrder: "Absteigende Reihenfolge" scratchpad: "Testumgebung" scratchpadDescription: "Die Testumgebung bietet einen Bereich für AiScript-Experimente. Dort kannst du AiScript schreiben, ausführen sowie dessen Auswirkungen auf Misskey überprüfen." +uiInspector: "UI-Inspektor" output: "Ausgabe" script: "Skript" disablePagesScript: "AiScript auf Seiten deaktivieren" @@ -652,6 +675,7 @@ smtpSecure: "Für SMTP-Verbindungen implizit SSL/TLS verwenden" smtpSecureInfo: "Schalte dies aus, falls du STARTTLS verwendest." testEmail: "Emailversand testen" wordMute: "Wortstummschaltung" +hardWordMute: "Harte Wort-Stummschaltung" regexpError: "Fehler in einem regulären Ausdruck" regexpErrorDescription: "Im regulären Ausdruck deiner in Zeile {line} von {tab}en Wortstummschaltungen ist ein Fehler aufgetreten:" instanceMute: "Instanzstummschaltungen" @@ -673,6 +697,7 @@ useGlobalSettingDesc: "Ist diese Option aktiviert, werden die Benachrichtigungse other: "Anderes" regenerateLoginToken: "Anmeldetoken regenerieren" regenerateLoginTokenDescription: "Den zur Anmeldung intern verwendeten Token regenerieren. Normalerweise wird dies nicht benötigt. Bei Regeneration werden alle Geräte ausgeloggt." +theKeywordWhenSearchingForCustomEmoji: "Das ist das Schlagwort beim Suchen von benutzerdefinierten Emojis." setMultipleBySeparatingWithSpace: "Trenne Elemente durch ein Leerzeichen um mehrere Einstellungen zu kofigurieren." fileIdOrUrl: "Datei-ID oder URL" behavior: "Verhalten" @@ -882,9 +907,12 @@ makeReactionsPublicDescription: "Jeder wird die Liste deiner gesendeten Reaktion classic: "Classic" muteThread: "Thread stummschalten" unmuteThread: "Threadstummschaltung aufheben" +followingVisibility: "Sichtbarkeit der Gefolgten" +followersVisibility: "Sichtbarkeit der Folgenden" continueThread: "Weiteren Threadverlauf anzeigen" deleteAccountConfirm: "Dein Benutzerkonto wird unwiderruflich gelöscht. Trotzdem fortfahren?" incorrectPassword: "Falsches Passwort." +incorrectTotp: "Das Einmalpasswort ist falsch oder abgelaufen." voteConfirm: "Wirklich für „{choice}“ abstimmen?" hide: "Inhalt verbergen" useDrawerReactionPickerForMobile: "Auf mobilen Geräten ausfahrbare Reaktionsauswahl anzeigen" @@ -909,6 +937,9 @@ oneHour: "Eine Stunde" oneDay: "Einen Tag" oneWeek: "Eine Woche" oneMonth: "1 Monat" +threeMonths: "3 Monate" +oneYear: "1 Jahr" +threeDays: "3 Tage" reflectMayTakeTime: "Es kann etwas dauern, bis sich dies widerspiegelt." failedToFetchAccountInformation: "Benutzerkontoinformationen konnten nicht abgefragt werden" rateLimitExceeded: "Versuchsanzahl überschritten" @@ -982,6 +1013,7 @@ neverShow: "Nicht wieder anzeigen" remindMeLater: "Vielleicht später" didYouLikeMisskey: "Gefällt dir Misskey?" pleaseDonate: "Misskey ist die kostenlose Software, die von {host} verwendet wird. Wir würden uns über Spenden freuen, damit dessen Entwicklung weitergeführt werden kann!" +correspondingSourceIsAvailable: "Der entsprechende Quellcode ist verfügbar unter {anchor}" roles: "Rollen" role: "Rolle" noRole: "Rolle nicht gefunden" @@ -1009,6 +1041,7 @@ thisPostMayBeAnnoyingHome: "Zur Startseite schicken" thisPostMayBeAnnoyingCancel: "Abbrechen" thisPostMayBeAnnoyingIgnore: "Trotzdem schicken" collapseRenotes: "Bereits gesehene Renotes verkürzt anzeigen" +collapseRenotesDescription: "Klappe Notizen ein, auf die du bereits reagiert oder die du renoted hast." internalServerError: "Serverinterner Fehler" internalServerErrorDescription: "Im Server ist ein unerwarteter Fehler aufgetreten." copyErrorInfo: "Fehlerdetails kopieren" @@ -1032,6 +1065,8 @@ resetPasswordConfirm: "Wirklich Passwort zurücksetzen?" sensitiveWords: "Sensible Wörter" sensitiveWordsDescription: "Die Notizsichtbarkeit aller Notizen, die diese Wörter enthalten, wird automatisch auf \"Startseite\" gesetzt. Durch Zeilenumbrüche können mehrere konfiguriert werden." sensitiveWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden." +prohibitedWords: "Verbotene Wörter" +prohibitedWordsDescription: "Aktiviert eine Fehlermeldung, wenn versucht wird, eine Notiz zu veröffentlichen, die das/die eingestellte(n) Wort(e) enthält. Mehrere Begriffe können durch Zeilenumbrüche getrennt festgelegt werden." prohibitedWordsDescription2: "Durch die Verwendung von Leerzeichen können AND-Verknüpfungen angegeben werden und durch das Umgeben von Schrägstrichen können reguläre Ausdrücke verwendet werden." hiddenTags: "Ausgeblendete Hashtags" hiddenTagsDescription: "Die hier eingestellten Tags werden nicht mehr in den Trends angezeigt. Mit der Umschalttaste können mehrere ausgewählt werden." @@ -1078,6 +1113,8 @@ preservedUsernames: "Reservierte Benutzernamen" preservedUsernamesDescription: "Gib zu reservierende Benutzernamen durch Zeilenumbrüche getrennt an. Diese werden für die Registrierung gesperrt, können aber von Administratoren zur manuellen Erstellung von Konten verwendet werden. Existierende Konten, die diese Namen bereits verwenden, werden nicht beeinträchtigt." createNoteFromTheFile: "Notiz für diese Datei schreiben" archive: "Archivieren" +archived: "Archiviert" +unarchive: "Dearchivieren" channelArchiveConfirmTitle: "{name} wirklich archivieren?" channelArchiveConfirmDescription: "Ein archivierter Kanal taucht nicht mehr in der Kanalliste oder in Suchergebnissen auf. Zudem können ihm keine Beiträge mehr hinzugefügt werden." thisChannelArchived: "Dieser Kanal wurde archiviert." @@ -1155,6 +1192,9 @@ confirmShowRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten confirmHideRepliesAll: "Dies ist eine unwiderrufliche Aktion. Wirklich Antworten von allen momentan gefolgten Benutzern nicht in der Chronik anzeigen?" externalServices: "Externe Dienste" sourceCode: "Quellcode" +sourceCodeIsNotYetProvided: "Der Quellcode ist noch nicht verfügbar. Kontaktiere den Administrator, um das Problem zu lösen." +repositoryUrl: "Repository URL" +repositoryUrlOrTarballRequired: "Wenn du kein Repository veröffentlicht hast, musst du stattdessen einen Tarball bereitstellen. Siehe .config/example.yml für weitere Informationen." impressum: "Impressum" impressumUrl: "Impressums-URL" impressumDescription: "In manchen Ländern, wie Deutschland und dessen Umgebung, ist die Angabe von Betreiberinformationen (ein Impressum) bei kommerziellem Betrieb zwingend." @@ -1176,15 +1216,82 @@ signupPendingError: "Beim Überprüfen der Mailadresse ist etwas schiefgelaufen. cwNotationRequired: "Ist \"Inhaltswarnung verwenden\" aktiviert, muss eine Beschreibung gegeben werden." doReaction: "Reagieren" code: "Code" +remainingN: "Verbleibend: {n}" +overwriteContentConfirm: "Bist du sicher, dass du den aktuellen Inhalt überschreiben willst?" +seasonalScreenEffect: "Saisonaler Bildschirmeffekt" decorate: "Dekorieren" addMfmFunction: "MFM hinzufügen" +enableQuickAddMfmFunction: "Erweiterte MFM-Auswahl anzeigen" sfx: "Soundeffekte" +soundWillBePlayed: "Es wird Ton wiedergegeben" +showReplay: "Wiederholung anzeigen" +ranking: "Rangliste" lastNDays: "Letzten {n} Tage" +backToTitle: "Zurück zum Startbildschirm" +enableHorizontalSwipe: "Wischen, um zwischen Tabs zu wechseln" +loading: "Laden" surrender: "Abbrechen" +gameRetry: "Erneut versuchen" +notUsePleaseLeaveBlank: "Leer lassen, wenn nicht verwendet" +useTotp: "Gib das Einmalpasswort ein" +useBackupCode: "Verwende die Backup-Codes" +launchApp: "Starte die App" +useNativeUIForVideoAudioPlayer: "Browser-Benutzeroberfläche für die Video- und Audiowiedergabe verwenden" +keepOriginalFilename: "Ursprünglichen Dateinamen beibehalten" +keepOriginalFilenameDescription: "Wenn diese Einstellung deaktiviert ist, wird der Dateiname beim Hochladen automatisch durch eine zufällige Zeichenfolge ersetzt." +noDescription: "Keine Beschreibung vorhanden" +tryAgain: "Bitte später erneut versuchen" +confirmWhenRevealingSensitiveMedia: "Das Anzeigen von sensiblen Medien bestätigen" +sensitiveMediaRevealConfirm: "Es könnte sich um sensible Medien handeln. Möchtest du sie anzeigen?" +createdLists: "Erstellte Listen" +createdAntennas: "Erstellte Antennen" +fromX: "Von {x}" +genEmbedCode: "Einbettungscode generieren" +noteOfThisUser: "Notizen dieses Benutzers" +clipNoteLimitExceeded: "Zu diesem Clip können keine weiteren Notizen hinzugefügt werden." +discard: "Verwerfen" +thereAreNChanges: "Es gibt {n} Änderung(en)" +signinWithPasskey: "Mit Passkey anmelden" +passkeyVerificationFailed: "Die Passkey-Verifizierung ist fehlgeschlagen." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "Die Verifizierung des Passkeys war erfolgreich, aber die passwortlose Anmeldung ist deaktiviert." +messageToFollower: "Nachricht an die Follower" +testCaptchaWarning: "Diese Funktion ist für CAPTCHA-Testzwecke gedacht.\n<strong>Nicht in einer Produktivumgebung verwenden.</strong>" +prohibitedWordsForNameOfUser: "Verbotene Begriffe für Benutzernamen" +prohibitedWordsForNameOfUserDescription: "Wenn eine Zeichenfolge aus dieser Liste im Namen eines Benutzers enthalten ist, wird der Benutzername abgelehnt. Benutzer mit Moderatorenrechten sind von dieser Einschränkung nicht betroffen." +yourNameContainsProhibitedWords: "Dein Name enthält einen verbotenen Begriff" +yourNameContainsProhibitedWordsDescription: "Der Name enthält eine verbotene Zeichenfolge. Wende dich an deinen Serveradministrator, wenn du diesen Namen verwenden möchtest." +pleaseSelectAccount: "Bitte Konto auswählen" +availableRoles: "Verfügbare Rollen" +_accountSettings: + requireSigninToViewContents: "Anmeldung erfordern, um Inhalte anzuzeigen" + requireSigninToViewContentsDescription1: "Erfordere eine Anmeldung, um alle Notizen und andere Inhalte anzuzeigen, die du erstellt hast. Dadurch wird verhindert, dass Crawler deine Informationen sammeln." + requireSigninToViewContentsDescription3: "Diese Einschränkungen gelten möglicherweise nicht für föderierte Inhalte von anderen Servern." + makeNotesFollowersOnlyBefore: "Macht frühere Notizen nur für Follower sichtbar" + makeNotesHiddenBefore: "Frühere Notizen privat machen" + mayNotEffectForFederatedNotes: "Dies hat möglicherweise keine Auswirkungen auf Notizen, die an andere Server föderiert werden." +_abuseUserReport: + forward: "Weiterleiten" + forwardDescription: "Leite die Meldung an einen entfernten Server als anonymes Systemkonto weiter." + accept: "Akzeptieren" + reject: "Ablehnen" _delivery: stop: "Gesperrt" _type: none: "Wird veröffentlicht" + manuallySuspended: "Manuell gesperrt" +_bubbleGame: + howToPlay: "Wie man spielt" + hold: "Halten" + _score: + score: "Spielstand" + scoreYen: "Verdienter Geldbetrag" + highScore: "Höchstpunktzahl" + maxChain: "Maximale Anzahl an Verkettungen" + yen: "{yen} Yen" + _howToPlay: + section1: "Passe die Position an und lasse das Objekt in das Spielfeld fallen." + section2: "Wenn sich zwei Objekte der gleichen Art berühren, verwandeln sie sich in ein anderes Objekt und du bekommst Punkte." + section3: "Das Spiel ist vorbei, wenn die Objekte aus dem Spielfeld herausragen. Versuche eine hohe Punktzahl zu erreichen, indem du die Objekte miteinander verschmelzt, ohne dass das Spielfeld überläuft!" _announcement: forExistingUsers: "Nur für existierende Nutzer" forExistingUsersDescription: "Ist diese Option aktiviert, wird diese Ankündigung nur Nutzern angezeigt, die zum Zeitpunkt der Ankündigung bereits registriert sind. Ist sie deaktiviert, wird sie auch Nutzern, die sich nach dessen Veröffentlichung registrieren, angezeigt." @@ -1228,8 +1335,18 @@ _initialTutorial: reply: "Klicke auf diesen Button, um auf eine Nachricht zu antworten. Es ist auch möglich, auf Antworten zu antworten und die Unterhaltung wie einen Thread fortzusetzen." _reaction: title: "Was sind Reaktionen?" + description: "Auf Notizen kann mit verschiedenen Emojis reagiert werden. Reaktionen ermöglichen es dir, Nuancen auszudrücken, die mit einem einfachen „Gefällt mir“ vielleicht nicht ausgedrückt werden können." + letsTryReacting: "Reaktionen können durch Klicken auf die Schaltfläche „+“ in der Notiz hinzugefügt werden. Versuche, auf diese Beispielnotiz zu reagieren!" reactToContinue: "Füge eine Reaktion hinzu, um fortzufahren." reactNotification: "Du erhältst Echtzeit-Benachrichtigungen, wenn jemand auf deine Notiz reagiert." + reactDone: "Du kannst eine Reaktion zurücknehmen, indem du auf den '-' Button drückst." + _timeline: + title: "So funktionieren die Chroniken" + home: "Du kannst Beiträge von den Konten sehen, denen du folgst." + local: "Du kannst Beiträge aller Benutzer auf diesem Server sehen." + social: "Notizen von der Startseite und der lokalen Chronik werden angezeigt." + global: "Du kannst Notizen von allen föderierten Servern sehen." + description2: "Du kannst jederzeit am oberen Rand des Bildschirms zwischen den jeweiligen Chroniken wechseln." _postNote: _visibility: description: "Du kannst einschränken, wer deine Notiz sehen kann." @@ -1237,8 +1354,16 @@ _initialTutorial: doNotSendConfidencialOnDirect1: "Sei vorsichtig, wenn du sensible Informationen verschickst!" _cw: title: "Inhaltswarnung" + _exampleNote: + note: "Ich hatte gerade einen Donut mit Schokoladenüberzug ðŸ©ðŸ˜‹" + _howToMakeAttachmentsSensitive: + tryThisFile: "Versuche, das angehängte Bild als sensibel zu markieren!" + method: "Um einen Anhang als sensibel zu kennzeichnen, klicke auf das Vorschaubild der Datei, um das Menü zu öffnen, und klicke auf „Als sensibel markieren“." + sensitiveSucceeded: "Wenn du Dateien anhängst, stelle bitte die Sensibilität entsprechend der Serverrichtlinien ein." + doItToContinue: "Markiere die angehängte Datei als sensibel, um fortzufahren." _done: title: "Du hast das Tutorial abgeschlossen! 🎉" + description: "Die hier beschriebenen Funktionen sind nur ein kleiner Teil dessen, was Misskey zu bieten hat; um mehr darüber zu erfahren, wie du Misskey benutzen kannst, besuche bitte {link}." _timelineDescription: local: "In der lokalen Chronik siehst du Notizen von allen Benutzern auf diesem Server." global: "In der globalen Chronik siehst du Notizen von allen föderierten Servern." @@ -1256,6 +1381,7 @@ _serverSettings: fanoutTimelineDescription: "Ist diese Option aktiviert, kann eine erhebliche Verbesserung im Abrufen von Chroniken und eine Reduzierung der Datenbankbelastung erzielt werden, im Gegenzug zu einer Steigerung in der Speichernutzung von Redis. Bei geringem Serverspeicher oder Serverinstabilität kann diese Option deaktiviert werden." fanoutTimelineDbFallback: "Auf die Datenbank zurückfallen" fanoutTimelineDbFallbackDescription: "Ist diese Option aktiviert, wird die Chronik auf zusätzliche Abfragen in der Datenbank zurückgreifen, wenn sich die Chronik nicht im Cache befindet. Eine Deaktivierung führt zu geringerer Serverlast, aber schränkt den Zeitraum der abrufbaren Chronik ein. " + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "Wenn über einen bestimmten Zeitraum keine Moderatorenaktivität festgestellt wird, wird diese Einstellung automatisch deaktiviert, um Spam zu verhindern." _accountMigration: moveFrom: "Von einem anderen Konto zu diesem migrieren" moveFromSub: "Alias für ein anderes Konto erstellen" @@ -1514,7 +1640,12 @@ _achievements: title: "Testüberfluss" description: "Betätige den Benachrichtigungstest mehrfach innerhalb einer extrem kurzen Zeitspanne" _tutorialCompleted: + title: "Misskey Grundkurs-Diplom" description: "Tutorial abgeschlossen" + _bubbleGameExplodingHead: + title: "🤯" + _bubbleGameDoubleExplodingHead: + title: "Doppel🤯" _role: new: "Rolle erstellen" edit: "Rolle bearbeiten" @@ -1555,6 +1686,7 @@ _role: gtlAvailable: "Kann auf die globale Chronik zugreifen" ltlAvailable: "Kann auf die lokale Chronik zugreifen" canPublicNote: "Kann öffentliche Notizen erstellen" + mentionMax: "Maximale Anzahl von Erwähnungen in einer Notiz" canInvite: "Erstellung von Einladungscodes für diese Instanz" inviteLimit: "Maximalanzahl an Einladungen" inviteLimitCycle: "Zyklus des Einladungslimits" @@ -1577,9 +1709,12 @@ _role: canSearchNotes: "Nutzung der Notizsuchfunktion" canUseTranslator: "Verwendung des Übersetzers" avatarDecorationLimit: "Maximale Anzahl an Profilbilddekorationen, die angebracht werden können" + canImportAntennas: "Importieren von Antennen erlauben" _condition: isLocal: "Lokaler Benutzer" isRemote: "Benutzer fremder Instanz" + isCat: "Katzen-Benutzer" + isBot: "Bot-Benutzer" createdLessThan: "Kontoerstellung liegt weniger als X zurück" createdMoreThan: "Kontoerstellung liegt mehr als X zurück" followersLessThanOrEq: "Hat X oder weniger Follower" @@ -1795,6 +1930,12 @@ _sfx: note: "Notizen" noteMy: "Meine Notizen" notification: "Benachrichtigungen" +_soundSettings: + driveFile: "Audiodatei aus dem Drive verwenden" + driveFileWarn: "Wähle eine Audiodatei aus dem Drive" + driveFileTypeWarn: "Diese Datei wird nicht unterstützt" + driveFileTypeWarnDescription: "Bitte wähle eine Audiodatei" + driveFileDurationWarn: "Audio zu lang." _ago: future: "Zukunft" justNow: "Gerade eben" @@ -1876,6 +2017,23 @@ _permissions: "write:flash": "Deine Plays bearbeiten oder löschen" "read:flash-likes": "Liste der Plays, die mir gefallen, lesen" "write:flash-likes": "Liste der Plays, die mir gefallen, bearbeiten" + "write:admin:delete-account": "Benutzerkonto löschen" + "write:admin:delete-all-files-of-a-user": "Alle Dateien eines Benutzers löschen" + "read:admin:index-stats": "Statistiken zu Datenbankindizes einsehen" + "read:admin:table-stats": "Statistiken zu Datenbanktabellen einsehen" + "read:admin:user-ips": "IP-Adressen von Benutzern anzeigen" + "read:admin:meta": "Metadaten der Instanz einsehen" + "write:admin:reset-password": "Benutzerpasswort zurücksetzen" + "write:admin:send-email": "E-Mail versenden" + "read:admin:server-info": "Serverinformationen anzeigen" + "read:admin:show-moderation-log": "Moderationsprotokoll einsehen" + "read:admin:show-user": "Private Benutzerinformationen einsehen" + "write:admin:invite-codes": "Einladungscodes verwalten" + "read:admin:invite-codes": "Einladungscodes anzeigen" + "write:admin:announcements": "Ankündigungen verwalten" + "read:admin:announcements": "Ankündigungen einsehen" + "write:admin:avatar-decorations": "Kann Avatar-Dekorationen verwalten" + "read:admin:avatar-decorations": "Avatar-Dekorationen ansehen" _auth: shareAccessTitle: "Verteilung von App-Berechtigungen" shareAccess: "Möchtest du „{name}“ authorisieren, auf dieses Benutzerkonto zugreifen zu können?" @@ -2115,6 +2273,7 @@ _notification: pollEnded: "Umfrageergebnisse sind verfügbar" newNote: "Neue Notiz" unreadAntennaNote: "Antenne {name}" + roleAssigned: "Rolle zugewiesen" emptyPushNotificationMessage: "Push-Benachrichtigungen wurden aktualisiert" achievementEarned: "Errungenschaft freigeschaltet" testNotification: "Testbenachrichtigung" @@ -2289,3 +2448,31 @@ _reversi: black: "Schwarz" white: "Weiß" total: "Gesamt" +_offlineScreen: + header: "Verbindung zum Server nicht möglich" +_urlPreviewSetting: + title: "Einstellungen der URL-Vorschau" + enable: "URL-Vorschau aktivieren" + timeout: "Zeitüberschreitung beim Abrufen der Vorschau (ms)" + maximumContentLength: "Maximale Content-Length (Bytes)" +_mediaControls: + playbackRate: "Wiedergabegeschwindigkeit" +_contextMenu: + title: "Kontextmenü" + app: "Anwendung" +_embedCodeGen: + title: "Einbettungscode anpassen" + header: "Kopfzeile anzeigen" + autoload: "Automatisch mehr laden (veraltet)" + maxHeight: "Maximale Höhe" + maxHeightDescription: "Der Wert 0 deaktiviert die Einstellung der maximalen Höhe. Gib einen Wert an, um zu verhindern, dass das Widget weiterhin vertikal vergrößert wird." + maxHeightWarn: "Die Begrenzung der maximalen Höhe ist deaktiviert (0). Wenn dies nicht beabsichtigt war, setze die maximale Höhe auf einen Wert fest." + applyToPreview: "Auf die Vorschau anwenden" + generateCode: "Einbettungscode generieren" + codeGenerated: "Der Code wurde generiert" + codeGeneratedDescription: "Füge den generierten Code in deine Website ein, um den Inhalt einzubetten." +_selfXssPrevention: + warning: "WARNUNG" + title: "„Füge in diesen Bereich etwas ein“ ist eine Betrugsmasche." + description1: "Wenn du hier etwas einfügst, könnte ein böswilliger Benutzer dein Konto übernehmen oder deine persönlichen Daten stehlen." + description3: "Weitere Informationen findest du hier. {link}" diff --git a/locales/en-US.yml b/locales/en-US.yml index 6ea7fb4f8d..69e6da1a6f 100644 --- a/locales/en-US.yml +++ b/locales/en-US.yml @@ -331,7 +331,7 @@ selectFile: "Select a file" selectFiles: "Select files" selectFolder: "Select a folder" selectFolders: "Select folders" -fileNotSelected: "" +fileNotSelected: "No file selected" renameFile: "Rename file" folderName: "Folder name" createFolder: "Create a folder" @@ -382,7 +382,6 @@ enableLocalTimeline: "Enable local timeline" enableGlobalTimeline: "Enable global timeline" disablingTimelinesInfo: "Adminstrators and Moderators will always have access to all timelines, even if they are not enabled." registration: "Register" -enableRegistration: "Enable new user registration" invite: "Invite" driveCapacityPerLocalAccount: "Drive capacity per local user" driveCapacityPerRemoteAccount: "Drive capacity per remote user" @@ -587,6 +586,7 @@ masterVolume: "Master volume" notUseSound: "Disable sound" useSoundOnlyWhenActive: "Output sounds only if Misskey is active." details: "Details" +renoteDetails: "Renote details" chooseEmoji: "Select an emoji" unableToProcess: "The operation could not be completed" recentUsed: "Recently used" @@ -947,6 +947,9 @@ oneHour: "One hour" oneDay: "One day" oneWeek: "One week" oneMonth: "One month" +threeMonths: "3 months" +oneYear: "1 year" +threeDays: "3 days" reflectMayTakeTime: "It may take some time for this to be reflected." failedToFetchAccountInformation: "Could not fetch account information" rateLimitExceeded: "Rate limit exceeded" @@ -1087,6 +1090,7 @@ retryAllQueuesConfirmTitle: "Really retry all?" retryAllQueuesConfirmText: "This will temporarily increase the server load." enableChartsForRemoteUser: "Generate remote user data charts" enableChartsForFederatedInstances: "Generate remote instance data charts" +enableStatsForFederatedInstances: "Receive remote server stats" showClipButtonInNoteFooter: "Add \"Clip\" to note action menu" reactionsDisplaySize: "Reaction display size" limitWidthOfReaction: "Limit the maximum width of reactions and display them in reduced size." @@ -1287,6 +1291,27 @@ passkeyVerificationFailed: "Passkey verification has failed." passkeyVerificationSucceededButPasswordlessLoginDisabled: "Passkey verification has succeeded but password-less login is disabled." messageToFollower: "Message to followers" target: "Target" +testCaptchaWarning: "This function is intended for CAPTCHA testing purposes.\n<strong>Do not use in a production environment.</strong>" +prohibitedWordsForNameOfUser: "Prohibited words for user names" +prohibitedWordsForNameOfUserDescription: "If any of the strings in this list are included in the user's name, the name will be denied. Users with moderator privileges are not affected by this restriction." +yourNameContainsProhibitedWords: "Your name contains prohibited words" +yourNameContainsProhibitedWordsDescription: "If you wish to use this name, please contact your server administrator." +thisContentsAreMarkedAsSigninRequiredByAuthor: "Set by the author to require login to view" +lockdown: "Lockdown" +pleaseSelectAccount: "Select an account" +availableRoles: "Available roles" +_accountSettings: + requireSigninToViewContents: "Require sign-in to view contents" + requireSigninToViewContentsDescription1: "Require login to view all notes and other content you have created. This will have the effect of preventing crawlers from collecting your information." + requireSigninToViewContentsDescription2: "Content will not be displayed in URL previews (OGP), embedded in web pages, or on servers that don't support note quotes." + requireSigninToViewContentsDescription3: "These restrictions may not apply to federated content from other remote servers." + makeNotesFollowersOnlyBefore: "Make past notes to be displayed only to followers" + makeNotesFollowersOnlyBeforeDescription: "While this feature is enabled, only followers can see notes past the set date and time or have been visible for a set time. When it is deactivated, the note publication status will also be restored." + makeNotesHiddenBefore: "Make past notes private" + makeNotesHiddenBeforeDescription: "While this feature is enabled, notes that are past the set date and time or have been visible only to you. When it is deactivated, the note publication status will also be restored." + mayNotEffectForFederatedNotes: "Notes federated to a remote server may not be affected." + notesHavePassedSpecifiedPeriod: "Note that the specified time has passed" + notesOlderThanSpecifiedDateAndTime: "Notes before the specified date and time" _abuseUserReport: forward: "Forward" forwardDescription: "Forward the report to a remote server as an anonymous system account." @@ -1431,6 +1456,7 @@ _serverSettings: reactionsBufferingDescription: "When enabled, performance during reaction creation will be greatly improved, reducing the load on the database. However, Redis memory usage will increase." inquiryUrl: "Inquiry URL" inquiryUrlDescription: "Specify a URL for the inquiry form to the server maintainer or a web page for the contact information." + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "If no moderator activity is detected for a while, this setting will be automatically turned off to prevent spam." _accountMigration: moveFrom: "Migrate another account to this one" moveFromSub: "Create alias to another account" @@ -2150,8 +2176,11 @@ _auth: permissionAsk: "This application requests the following permissions" pleaseGoBack: "Please go back to the application" callback: "Returning to the application" + accepted: "Access granted" denied: "Access denied" + scopeUser: "Operate as the following user" pleaseLogin: "Please log in to authorize applications." + byClickingYouWillBeRedirectedToThisUrl: "When access is granted, you will automatically be redirected to the following URL" _antennaSources: all: "All notes" homeTimeline: "Notes from followed users" @@ -2196,7 +2225,7 @@ _widgets: _userList: chooseList: "Select a list" clicker: "Clicker" - birthdayFollowings: "Users who celebrate their birthday today" + birthdayFollowings: "Today's Birthdays" _cw: hide: "Hide" show: "Show content" @@ -2485,6 +2514,8 @@ _webhookSettings: abuseReport: "When received a new report" abuseReportResolved: "When resolved report" userCreated: "When user is created" + inactiveModeratorsWarning: "When moderators have been inactive for a while" + inactiveModeratorsInvitationOnlyChanged: "When a moderator has been inactive for a while, and the server is changed to invitation-only" deleteConfirm: "Are you sure you want to delete the Webhook?" testRemarks: "Click the button to the right of the switch to send a test Webhook with dummy data." _abuseReport: @@ -2701,3 +2732,9 @@ _embedCodeGen: generateCode: "Generate embed code" codeGenerated: "The code has been generated" codeGeneratedDescription: "Paste the generated code into your website to embed the content." +_selfXssPrevention: + warning: "WARNING" + title: "\"Paste something on this screen\" is all a scam." + description1: "If you paste something here, a malicious user could hijack your account or steal your personal information." + description2: "If you do not understand exactly what you are trying to paste, %cstop working right now and close this window." + description3: "For more information, please refer to this. {link}" diff --git a/locales/es-ES.yml b/locales/es-ES.yml index d574999e40..a4ec114b15 100644 --- a/locales/es-ES.yml +++ b/locales/es-ES.yml @@ -8,6 +8,8 @@ search: "Buscar" notifications: "Notificaciones" username: "Nombre de usuario" password: "Contraseña" +initialPasswordForSetup: "Contraseña para iniciar la inicialización" +initialPasswordIsIncorrect: "La contraseña para iniciar la configuración inicial es incorrecta." forgotPassword: "Olvidé mi contraseña" fetchingAsApObject: "Buscando en el fediverso" ok: "OK" @@ -371,7 +373,6 @@ enableLocalTimeline: "Habilitar linea de tiempo local" enableGlobalTimeline: "Habilitar linea de tiempo global" disablingTimelinesInfo: "Aunque se desactiven estas lineas de tiempo, por conveniencia el administrador y los moderadores pueden seguir usándolos" registration: "Registro" -enableRegistration: "Permitir nuevos registros" invite: "Invitar" driveCapacityPerLocalAccount: "Capacidad del drive por usuario local" driveCapacityPerRemoteAccount: "Capacidad del drive por usuario remoto" @@ -502,6 +503,8 @@ uiLanguage: "Idioma de visualización de la interfaz" aboutX: "Acerca de {x}" emojiStyle: "Estilo de emoji" native: "Nativo" +menuStyle: "Diseño del menú" +style: "Diseño" showNoteActionsOnlyHover: "Mostrar acciones de la nota sólo al pasar el cursor" showReactionsCount: "Mostrar el número de reacciones en las notas" noHistory: "No hay datos en el historial" @@ -925,6 +928,9 @@ oneHour: "1 hora" oneDay: "1 dÃa" oneWeek: "1 semana" oneMonth: "1 mes" +threeMonths: "Tres meses" +oneYear: "Un año" +threeDays: "Tres dÃas" reflectMayTakeTime: "Puede pasar un tiempo hasta que se reflejen los cambios" failedToFetchAccountInformation: "No se pudo obtener información de la cuenta" rateLimitExceeded: "Se excedió el lÃmite de peticiones" @@ -1240,6 +1246,14 @@ useNativeUIForVideoAudioPlayer: "Usar la interfaz del navegador cuando se reprod keepOriginalFilename: "Mantener el nombre original del archivo" noDescription: "No hay descripción" alwaysConfirmFollow: "Confirmar siempre cuando se sigue a alguien" +inquiry: "Contacto" +tryAgain: "Por favor , inténtalo de nuevo" +performance: "Rendimiento" +unknownWebAuthnKey: "Esto no se ha registrado llave maestra." +messageToFollower: "Mensaje a seguidores" +_abuseUserReport: + accept: "Acepte" + reject: "repudio" _delivery: stop: "Suspendido" _type: @@ -2340,6 +2354,7 @@ _notification: roleAssigned: "Rol asignado" achievementEarned: "Logro desbloqueado" login: "Iniciar sesión" + test: "Pruebas de nofiticaciones" app: "Notificaciones desde aplicaciones" _actions: followBack: "Te sigue de vuelta" @@ -2398,6 +2413,8 @@ _webhookSettings: renote: "Cuando reciba un \"re-note\"" reaction: "Cuando se recibe una reacción" mention: "Cuando hay una mención" + _systemEvents: + userCreated: "Cuando se crea el usuario." _abuseReport: _notificationRecipient: _recipientType: diff --git a/locales/fr-FR.yml b/locales/fr-FR.yml index a7060c06fc..b105a86b5e 100644 --- a/locales/fr-FR.yml +++ b/locales/fr-FR.yml @@ -8,6 +8,9 @@ search: "Rechercher" notifications: "Notifications" username: "Nom d’utilisateur·rice" password: "Mot de passe" +initialPasswordForSetup: "Mot de passe initial pour la configuration" +initialPasswordIsIncorrect: "Mot de passe initial pour la configuration est incorrecte" +initialPasswordForSetupDescription: "Utilisez le mot de passe que vous avez entré pour le fichier de configuration si vous avez installé Misskey vous-même.\nSi vous utilisez un service d'hébergement Misskey, utilisez le mot de passe fourni.\nSi vous n'avez pas défini de mot de passe, laissez le champ vide pour continuer." forgotPassword: "Mot de passe oublié" fetchingAsApObject: "Récupération depuis le fédiverse …" ok: "OK" @@ -60,6 +63,7 @@ copyFileId: "Copier l'identifiant du fichier" copyFolderId: "Copier l'identifiant du dossier" copyProfileUrl: "Copier l'URL du profil" searchUser: "Chercher un·e utilisateur·rice" +searchThisUsersNotes: "Cherchez les notes de cet·te utilisateur·rice" reply: "Répondre" loadMore: "Afficher plus …" showMore: "Voir plus" @@ -108,6 +112,7 @@ enterEmoji: "Insérer un émoji" renote: "Renoter" unrenote: "Annuler la Renote" renoted: "Renoté !" +renotedToX: "Renoté en {name}" cantRenote: "Ce message ne peut pas être renoté." cantReRenote: "Impossible de renoter une Renote." quote: "Citer" @@ -151,6 +156,7 @@ editList: "Modifier la liste" selectChannel: "Sélectionner un canal" selectAntenna: "Sélectionner une antenne" editAntenna: "Modifier l'antenne" +createAntenna: "Créer une antenne" selectWidget: "Sélectionner un widget" editWidgets: "Modifier les widgets" editWidgetsExit: "Valider les modifications" @@ -177,6 +183,7 @@ addAccount: "Ajouter un compte" reloadAccountsList: "Rafraichir la liste des comptes" loginFailed: "Échec de la connexion" showOnRemote: "Voir sur l’instance distante" +continueOnRemote: "Continuer sur l'instance distante" general: "Général" wallpaper: "Fond d’écran" setWallpaper: "Définir le fond d’écran" @@ -187,6 +194,7 @@ followConfirm: "Êtes-vous sûr·e de vouloir suivre {name} ?" proxyAccount: "Compte proxy" proxyAccountDescription: "Un compte proxy se comporte, dans certaines conditions, comme un·e abonné·e distant·e pour les utilisateurs d'autres instances. Par exemple, quand un·e utilisateur·rice ajoute un·e utilisateur·rice distant·e à une liste, ses notes ne seront pas visibles sur l'instance si personne ne suit cet·te utilisateur·rice. Le compte proxy va donc suivre cet·te utilisateur·rice pour que ses notes soient acheminées." host: "Serveur distant" +selectSelf: "Sélectionner manuellement" selectUser: "Sélectionner un·e utilisateur·rice" recipient: "Destinataire" annotation: "Commentaires" @@ -320,6 +328,7 @@ renameFolder: "Renommer le dossier" deleteFolder: "Supprimer le dossier" folder: "Dossier" addFile: "Ajouter un fichier" +showFile: "Voir les fichiers" emptyDrive: "Le Disque est vide" emptyFolder: "Le dossier est vide" unableToDelete: "Suppression impossible" @@ -362,7 +371,6 @@ enableLocalTimeline: "Activer le fil local" enableGlobalTimeline: "Activer le fil global" disablingTimelinesInfo: "Même si vous désactivez ces fils, les administrateur·rice·s et les modérateur·rice·s pourront toujours y accéder." registration: "S’inscrire" -enableRegistration: "Autoriser les nouvelles inscriptions" invite: "Inviter" driveCapacityPerLocalAccount: "Capacité de stockage du Disque par utilisateur local" driveCapacityPerRemoteAccount: "Capacité de stockage du Disque par utilisateur distant" @@ -430,10 +438,11 @@ token: "Jeton" 2fa: "Authentification à deux facteurs" setupOf2fa: "Configuration de l’authentification à deux facteurs" totp: "Application d'authentification" -totpDescription: "Entrez un mot de passe à usage unique à l'aide d'une application d'authentification" +totpDescription: "Entrer un mot de passe à usage unique à l'aide d'une application d'authentification" moderator: "Modérateur·rice·s" moderation: "Modérations" moderationNote: "Note de modération" +moderationNoteDescription: "Vous pouvez remplir des notes qui seront partagés seulement entre modérateurs." addModerationNote: "Ajouter une note de modération" moderationLogs: "Journal de modération" nUsersMentioned: "{n} utilisateur·rice·s mentionné·e·s" @@ -493,6 +502,10 @@ uiLanguage: "Langue d’affichage de l’interface" aboutX: "À propos de {x}" emojiStyle: "Style des émojis" native: "Natif" +menuStyle: "Style du menu" +style: "Style" +drawer: "Sélecteur" +popup: "Pop-up" showNoteActionsOnlyHover: "Afficher les actions de note uniquement au survol" showReactionsCount: "Afficher le nombre de réactions des notes" noHistory: "Pas d'historique" @@ -575,6 +588,7 @@ ascendingOrder: "Ascendant" descendingOrder: "Descendant" scratchpad: "ScratchPad" scratchpadDescription: "ScratchPad fournit un environnement expérimental pour AiScript. Vous pouvez vérifier la rédaction de votre code, sa bonne exécution et le résultat de son interaction avec Misskey." +uiInspector: "Inspecteur UI" output: "Sortie" script: "Script" disablePagesScript: "Désactiver AiScript sur les Pages" @@ -618,7 +632,7 @@ description: "Description" describeFile: "Ajouter une description d'image" enterFileDescription: "Saisissez une description" author: "Auteur·rice" -leaveConfirm: "Vous avez des modifications non-sauvegardées. Voulez-vous les ignorer ?" +leaveConfirm: "Vous avez des modifications non sauvegardées. Voulez-vous les ignorer ?" manage: "Gestion" plugins: "Extensions" preferencesBackups: "Sauvegarder les paramètres" @@ -828,6 +842,7 @@ administration: "Gestion" accounts: "Comptes" switch: "Remplacer" noMaintainerInformationWarning: "Informations administrateur non configurées." +noInquiryUrlWarning: "L'URL demandé n'est pas définie" noBotProtectionWarning: "La protection contre les bots n'est pas configurée." configure: "Configurer" postToGallery: "Publier dans la galerie" @@ -892,6 +907,7 @@ followersVisibility: "Visibilité des abonnés" continueThread: "Afficher la suite du fil" deleteAccountConfirm: "Votre compte sera supprimé. Êtes vous certain ?" incorrectPassword: "Le mot de passe est incorrect." +incorrectTotp: "Le mot de passe à usage unique est incorrect ou a expiré." voteConfirm: "Confirmez-vous votre vote pour « {choice} » ?" hide: "Masquer" useDrawerReactionPickerForMobile: "Afficher le sélecteur de réactions en tant que panneau sur mobile" @@ -916,6 +932,9 @@ oneHour: "1 heure" oneDay: "1 jour" oneWeek: "1 semaine" oneMonth: "Un mois" +threeMonths: "3 mois" +oneYear: "1 an" +threeDays: "3 jours" reflectMayTakeTime: "Cela peut prendre un certain temps avant que cela ne se termine." failedToFetchAccountInformation: "Impossible de récupérer les informations du compte." rateLimitExceeded: "Limite de taux dépassée" @@ -923,7 +942,7 @@ cropImage: "Recadrer l'image" cropImageAsk: "Voulez-vous recadrer cette image ?" cropYes: "Rogner" cropNo: "Utiliser en l'état" -file: "Fichiers" +file: "Fichier" recentNHours: "Dernières {n} heures" recentNDays: "Derniers {n} jours" noEmailServerWarning: "Serveur de courrier non configuré." @@ -1055,6 +1074,7 @@ retryAllQueuesConfirmTitle: "Vraiment réessayer ?" retryAllQueuesConfirmText: "Cela peut augmenter temporairement la charge du serveur." enableChartsForRemoteUser: "Générer les graphiques pour les utilisateurs distants" enableChartsForFederatedInstances: "Générer les graphiques pour les instances distantes" +enableStatsForFederatedInstances: "Recevoir les statistiques des instances distantes" showClipButtonInNoteFooter: "Ajouter « Clip » au menu d'action de la note" reactionsDisplaySize: "Taille de l'affichage des réactions" limitWidthOfReaction: "Limiter la largeur maximale des réactions et les afficher en taille réduite" @@ -1102,6 +1122,8 @@ preventAiLearning: "Refuser l'usage dans l'apprentissage automatique d'IA géné preventAiLearningDescription: "Demander aux robots d'indexation de ne pas utiliser le contenu publié, tel que les notes et les images, dans l'apprentissage automatique d'IA générative. Cela est réalisé en incluant le drapeau « noai » dans la réponse HTML. Une prévention complète n'est toutefois pas possible, car il est au robot d'indexation de respecter cette demande." options: "Options" specifyUser: "Spécifier l'utilisateur·rice" +openTagPageConfirm: "Ouvrir une page d'hashtags ?" +specifyHost: "Spécifier un serveur distant" failedToPreviewUrl: "Aperçu d'URL échoué" update: "Mettre à jour" rolesThatCanBeUsedThisEmojiAsReaction: "Rôles qui peuvent utiliser cet émoji comme réaction" @@ -1222,13 +1244,55 @@ enableHorizontalSwipe: "Glisser pour changer d'onglet" loading: "Chargement en cours" surrender: "Annuler" gameRetry: "Réessayer" +notUsePleaseLeaveBlank: "Laisser vide si non utilisé" +useTotp: "Entrer un mot de passe à usage unique" +useBackupCode: "Utiliser le codes de secours" launchApp: "Lancer l'app" +useNativeUIForVideoAudioPlayer: "Lire les vidéos et audios en utilisant l'UI du navigateur" +keepOriginalFilename: "Garder le nom original du fichier" +keepOriginalFilenameDescription: "Si vous désactivez ce paramètre, les noms de fichiers seront automatiquement remplacés par des noms aléatoires lorsque vous téléchargerez des fichiers." +noDescription: "Il n'y a pas de description" +alwaysConfirmFollow: "Confirmer lors d'un abonnement" inquiry: "Contact" +tryAgain: "Veuillez réessayer plus tard" +confirmWhenRevealingSensitiveMedia: "Confirmer pour révéler du contenu sensible" +sensitiveMediaRevealConfirm: "Ceci pourrait être du contenu sensible. Voulez-vous l'afficher ?" +createdLists: "Listes créées" +createdAntennas: "Antennes créées" +fromX: "De {x}" +genEmbedCode: "Générer le code d'intégration" +noteOfThisUser: "Notes de cet·te utilisateur·rice" +clipNoteLimitExceeded: "Aucune note supplémentaire ne peut être ajoutée à ce clip." +performance: "Performance" +modified: "Modifié" +discard: "Annuler" +thereAreNChanges: "Il y a {n} modification(s)" +signinWithPasskey: "Se connecter avec une clé d'accès" +unknownWebAuthnKey: "Clé d'accès inconnue." +passkeyVerificationFailed: "La vérification de la clé d'accès a échoué." +passkeyVerificationSucceededButPasswordlessLoginDisabled: "La vérification de la clé d'accès a réussi, mais la connexion sans mot de passe est désactivée." +messageToFollower: "Message aux abonné·es" +target: "Destinataire" +prohibitedWordsForNameOfUser: "Mots interdits pour les noms d'utilisateur·rices" +lockdown: "Verrouiller" +pleaseSelectAccount: "Sélectionner un compte" +availableRoles: "Rôles disponibles" +_abuseUserReport: + forward: "Transférer" + forwardDescription: "Transférer le signalement vers une instance distante en tant qu'anonyme." + resolve: "Résoudre" + accept: "Accepter" + reject: "Rejeter" + resolveTutorial: "Si le signalement est légitime dans son contenu, sélectionnez « Accepter » pour marquer le cas comme résolu par l'affirmative.\nSi le contenu du rapport n'est pas légitime, sélectionnez « Rejeter » pour marquer le cas comme résolu par la négative." _delivery: status: "Statut de la diffusion" stop: "Suspendu·e" + resume: "Reprendre" _type: none: "Publié" + manuallySuspended: "Suspendre manuellement" + goneSuspended: "L'instance est suspendue en raison de la suppression de ce dernier" + autoSuspendedForNotResponding: "L'instance est suspendue car elle ne répond pas" _bubbleGame: howToPlay: "Comment jouer" hold: "Réserver" @@ -1239,6 +1303,7 @@ _bubbleGame: maxChain: "Nombre maximum de chaînes" yen: "{yen} yens" estimatedQty: "{qty} pièces" + scoreSweets: "{onigiriQtyWithUnit} Onigiri(s)" _announcement: forExistingUsers: "Pour les utilisateurs existants seulement" needConfirmationToRead: "Exiger la confirmation de la lecture" @@ -1258,6 +1323,7 @@ _initialAccountSetting: profileSetting: "Paramètres du profil" privacySetting: "Paramètres de confidentialité" initialAccountSettingCompleted: "Configuration du profil terminée avec succès !" + haveFun: "Profitez de {name} !" youCanContinueTutorial: "Vous pouvez procéder au tutoriel sur l'utilisation de {name}(Misskey) ou vous arrêter ici et commencer à l'utiliser immédiatement." startTutorial: "Démarrer le tutoriel" skipAreYouSure: "Désirez-vous ignorer la configuration du profil ?" @@ -1351,18 +1417,60 @@ _achievements: flavor: "Passez un bon moment avec Misskey !" _notes10: title: "Quelques notes" + description: "Poster 10 notes" _notes100: title: "Beaucoup de notes" + description: "Poster 100 notes" + _notes500: + title: "Couvert de notes" + description: "Poster 500 notes" + _notes1000: + title: "Une montagne de notes" + description: "Poster 1000 notes" + _notes5000: + title: "Débordement de notes" + description: "Poster 5 000 notes" + _notes10000: + title: "Super note" + description: "Poster 10 000 notes" + _notes20000: + title: "Encore... plus... de... notes..." + description: "Poster 20 000 notes" + _notes30000: + title: "Notes notes notes !" + description: "Poster 30 000 notes" + _notes40000: + title: "Usine de notes" + description: "Poster 40 000 notes" + _notes50000: + title: "Planète des notes" + description: "Poster 50 000 notes" + _notes60000: + title: "Quasar de note" + description: "Poster 50 000 notes" + _notes70000: + title: "Trou noir de notes" + description: "Poster 70 000 notes" + _notes80000: + title: "Galaxie de notes" + description: "Poster 80 000 notes" + _notes90000: + title: "Univers de notes" + description: "Poster 90 000 notes" _notes100000: title: "ALL YOUR NOTE ARE BELONG TO US" + description: "Poster 100 000 notes" + flavor: "Avez-vous tant de choses à dire ?" _login3: - title: "Débutant â… " + title: "Débutant I" description: "Se connecter pour un total de 3 jours" + flavor: "Dès maintenant, appelez-moi Misskeynaute" _login7: - title: "Débutant â…¡" + title: "Débutant II" description: "Se connecter pour un total de 7 jours" + flavor: "On s'habitue ?" _login15: - title: "Débutant â…¢" + title: "Débutant III" description: "Se connecter pour un total de 15 jours" _login30: title: "Misskeynaute I" @@ -1386,6 +1494,7 @@ _achievements: _login500: title: "Expert I" description: "Se connecter pour un total de 500 jours" + flavor: "Non, mes amis, j'aime les notes" _login600: title: "Expert II" description: "Se connecter pour un total de 600 jours" @@ -1393,11 +1502,18 @@ _achievements: title: "Expert III" description: "Se connecter pour un total de 700 jours" _login800: + title: "Maître des notes I" description: "Se connecter pour un total de 800 jours" _login900: + title: "Maître des notes II" description: "Se connecter pour un total de 900 jours" _login1000: + title: "Maître des notes III" + description: "Se connecter pour un total de 1 000 jours" flavor: "Merci d'utiliser Misskey !" + _noteClipped1: + title: "Je... dois... clip..." + description: "Ajouter sa première note aux clips" _profileFilled: title: "Bien préparé" description: "Configuration de votre profil" @@ -1456,21 +1572,31 @@ _achievements: _driveFolderCircularReference: title: "Référence circulaire" _setNameToSyuilo: + title: "Complexe de dieu" description: "Vous avez spécifié « syuilo » comme nom" _passedSinceAccountCreated1: title: "Premier anniversaire" + description: "Un an est passé depuis la création du compte" _passedSinceAccountCreated2: title: "Second anniversaire" + description: "Deux ans sont passés depuis la création du compte" _passedSinceAccountCreated3: title: "3ème anniversaire" + description: "Trois ans sont passés depuis la création du compte" _loggedInOnBirthday: title: "Joyeux Anniversaire !" description: "Vous vous êtes connecté à la date de votre anniversaire" _loggedInOnNewYearsDay: title: "Bonne année !" + description: "Vous vous êtes connecté le premier jour de l'année" + flavor: "Merci pour le soutient continue sur cette instance." _cookieClicked: + title: "Jeu de clic sur des cookies" + description: "Cliqué sur un cookie" flavor: "Attendez une minute, vous êtes sur le mauvais site web ?" _brainDiver: + title: "Brain Diver" + description: "Poster le lien sur Brain Diver" flavor: "Misskey-Misskey La-Tu-Ma" _smashTestNotificationButton: title: "Débordement de tests" @@ -1478,6 +1604,11 @@ _achievements: _tutorialCompleted: title: "Diplôme de la course élémentaire de Misskey" description: "Terminer le tutoriel" + _bubbleGameExplodingHead: + title: "🤯" + description: "Le plus gros objet du jeu de bulles" + _bubbleGameDoubleExplodingHead: + title: "Double🤯" _role: new: "Nouveau rôle" edit: "Modifier le rôle" @@ -1508,9 +1639,11 @@ _role: canManageCustomEmojis: "Gestion des émojis personnalisés" canManageAvatarDecorations: "Gestion des décorations d'avatar" driveCapacity: "Capacité de stockage du Disque" + antennaMax: "Nombre maximum d'antennes" wordMuteMax: "Nombre maximal de caractères dans le filtre de mots" canUseTranslator: "Usage de la fonctionnalité de traduction" avatarDecorationLimit: "Nombre maximal de décorations d'avatar" + canImportAntennas: "Autoriser l'importation d'antennes" _sensitiveMediaDetection: description: "L'apprentissage automatique peut être utilisé pour détecter automatiquement les médias sensibles à modérer. La sollicitation des serveurs augmente légèrement." sensitivity: "Sensibilité de la détection" @@ -1793,6 +1926,29 @@ _permissions: "write:gallery": "Éditer la galerie" "read:gallery-likes": "Voir les mentions « J'aime » dans la galerie" "write:gallery-likes": "Gérer les mentions « J'aime » dans la galerie" + "read:flash": "Voir le Play" + "write:flash": "Modifier le Play" + "read:flash-likes": "Lire vos mentions j'aime des Play" + "write:flash-likes": "Modifier vos mentions j'aime des Play" + "read:admin:abuse-user-reports": "Voir les utilisateurs signalés" + "write:admin:delete-account": "Supprimer le compte d'utilisateur" + "write:admin:delete-all-files-of-a-user": "Supprimer tous les fichiers d'un utilisateur" + "read:admin:index-stats": "Voir les statistiques sur les index de base de données" + "read:admin:table-stats": "Voir les statistiques sur les index de base de données" + "read:admin:user-ips": "Voir l'adresse IP de l'utilisateur" + "read:admin:meta": "Voir les métadonnées de l'instance" + "write:admin:reset-password": "Réinitialiser le mot de passe de l'utilisateur" + "write:admin:resolve-abuse-user-report": "Résoudre le signalement d'un utilisateur" + "write:admin:send-email": "Envoyer un mail" + "read:admin:server-info": "Voir les informations de l'instance" + "read:admin:show-moderation-log": "Voir les logs de modération" + "read:admin:show-user": "Voir les informations privées de l'utilisateur" + "write:admin:suspend-user": "Suspendre l'utilisateur" + "write:admin:unset-user-avatar": "Retirer l'avatar de l'utilisateur" + "write:admin:unset-user-banner": "Retirer la bannière de l'utilisateur" + "write:admin:unsuspend-user": "Lever la suspension d'un utilisateur" + "write:admin:meta": "Gérer les métadonnées de l'instance" + "write:admin:roles": "Gérer les rôles" _auth: shareAccess: "Autoriser \"{name}\" à accéder à votre compte ?" shareAccessAsk: "Voulez-vous vraiment autoriser cette application à accéder à votre compte?" @@ -1944,7 +2100,16 @@ _timelines: social: "Social" global: "Global" _play: + new: "Créer un Play" + edit: "Modifier un Play" + created: "Play créé" + updated: "Play édité" + deleted: "Play supprimé" + pageSetting: "Configuration du Play" + editThisPage: "Modifier ce Play" viewSource: "Afficher la source" + my: "Mes Play" + liked: "Play aimés" featured: "Populaire" title: "Titre" script: "Script" @@ -2018,10 +2183,13 @@ _notification: achievementEarned: "Accomplissement déverrouillé" testNotification: "Tester la notification" reactedBySomeUsers: "{n} utilisateur·rice·s ont réagi" + likedBySomeUsers: "{n} utilisateurs ont aimé votre note" renotedBySomeUsers: "{n} utilisateur·rice·s ont renoté" followedBySomeUsers: "{n} utilisateur·rice·s se sont abonné·e·s à vous" + login: "Quelqu'un s'est connecté" _types: all: "Toutes" + note: "Nouvelles notes" follow: "Nouvel·le abonné·e" mention: "Mentions" reply: "Réponses" @@ -2071,11 +2239,14 @@ _drivecleaner: orderByCreatedAtAsc: "Date d'ajout ascendante" _webhookSettings: name: "Nom" + secret: "Secret" + trigger: "Activateur" active: "Activé" _abuseReport: _notificationRecipient: _recipientType: mail: "E-mail " + keywords: "Mots clés " _moderationLogTypes: createRole: "Rôle créé" deleteRole: "Rôle supprimé" @@ -2112,6 +2283,7 @@ _moderationLogTypes: deleteAvatarDecoration: "Décoration d'avatar supprimée" unsetUserAvatar: "Supprimer l'avatar de l'utilisateur·rice" unsetUserBanner: "Supprimer la bannière de l'utilisateur·rice" + deleteFlash: "Supprimer le Play" _fileViewer: title: "Détails du fichier" type: "Type du fichier" @@ -2175,5 +2347,20 @@ _dataSaver: title: "Mise en évidence du code" description: "Si la notation de mise en évidence du code est utilisée, par exemple dans la MFM, elle ne sera pas chargée tant qu'elle n'aura pas été tapée. La mise en évidence du code nécessite le chargement du fichier de définition de chaque langue à mettre en évidence, mais comme ces fichiers ne sont plus chargés automatiquement, on peut s'attendre à une réduction du trafic de données." _reversi: + reversi: "Reversi" + blackIs: "{name} joue les noirs" + rules: "Règles" waitingBoth: "Préparez-vous" + myTurn: "C’est votre tour" + turnOf: "C'est le tour de {name}" + pastTurnOf: "Tour de {name}" + surrender: "Se rendre" + surrendered: "Par abandon" total: "Total" + playing: "En cours" + lookingForPlayer: "Recherche d'adversaire" +_mediaControls: + playbackRate: "Vitesse de lecture" +_embedCodeGen: + title: "Personnaliser le code d'intégration" + generateCode: "Générer le code d'intégration" diff --git a/locales/hu-HU.yml b/locales/hu-HU.yml index acc27ed092..d0fdc027e9 100644 --- a/locales/hu-HU.yml +++ b/locales/hu-HU.yml @@ -1,5 +1,5 @@ --- -_lang_: "Japán" +_lang_: "Magyar" monthAndDay: "{month}.{day}." search: "Keresés" notifications: "ÉrtesÃtések" diff --git a/locales/id-ID.yml b/locales/id-ID.yml index ce3958b167..fe3f207618 100644 --- a/locales/id-ID.yml +++ b/locales/id-ID.yml @@ -196,6 +196,7 @@ followConfirm: "Apakah kamu yakin ingin mengikuti {name}?" proxyAccount: "Akun proksi" proxyAccountDescription: "Akun proksi merupakan sebuah akun yang bertindak sebagai pengikut instansi luar untuk pengguna dalam kondisi tertentu. Sebagai contoh, ketika pengguna menambahkan seorang pengguna instansi luar ke dalam daftar, aktivitas dari pengguna instansi luar tidak akan disampaikan ke instansi apabila tidak ada pengguna lokal yang mengikuti pengguna tersebut, dengan begitu akun proksilah yang akan mengikutinya." host: "Host" +selectSelf: "Pilih diri sendiri" selectUser: "Pilih pengguna" recipient: "Penerima" annotation: "Keterangan konten" @@ -232,6 +233,7 @@ blockedInstances: "Instansi terblokir" blockedInstancesDescription: "Daftar nama host dari instansi yang diperlukan untuk diblokir. Instansi yang didaftarkan tidak akan dapat berkomunikasi dengan instansi ini." silencedInstances: "Instansi yang disenyapkan" silencedInstancesDescription: "Daftar nama host dari instansi yang ingin kamu senyapkan. Semua akun dari instansi yang terdaftar akan diperlakukan sebagai disenyapkan. Hal ini membuat akun hanya dapat membuat permintaan mengikuti, dan tidak dapat menyebutkan akun lokal apabila tidak mengikuti. Hal ini tidak akan mempengaruhi instansi yang diblokir." +federationAllowedHosts: "Server yang membolehkan federasi" muteAndBlock: "Bisukan / Blokir" mutedUsers: "Pengguna yang dibisukan" blockedUsers: "Pengguna yang diblokir" @@ -330,6 +332,7 @@ renameFolder: "Ubah nama folder" deleteFolder: "Hapus folder" folder: "Folder" addFile: "Tambahkan berkas" +showFile: "Tampilkan berkas" emptyDrive: "Drive kosong" emptyFolder: "Folder kosong" unableToDelete: "Tidak dapat menghapus" @@ -372,7 +375,6 @@ enableLocalTimeline: "Nyalakan lini masa lokal" enableGlobalTimeline: "Nyalakan lini masa global" disablingTimelinesInfo: "Admin dan Moderator akan selalu memiliki akses ke semua lini masa meskipun lini masa tersebut tidak diaktifkan." registration: "Pendaftaran" -enableRegistration: "Nyalakan pendaftaran pengguna baru" invite: "Undang" driveCapacityPerLocalAccount: "Kapasitas drive per pengguna lokal" driveCapacityPerRemoteAccount: "Kapasitas drive per pengguna remote" @@ -504,6 +506,8 @@ uiLanguage: "Bahasa antarmuka pengguna" aboutX: "Tentang {x}" emojiStyle: "Gaya emoji" native: "Native" +menuStyle: "Gaya menu" +style: "Gaya" showNoteActionsOnlyHover: "Hanya tampilkan aksi catatan saat ditunjuk" showReactionsCount: "Lihat jumlah reaksi dalam catatan" noHistory: "Tidak ada riwayat" @@ -927,6 +931,9 @@ oneHour: "1 Jam" oneDay: "1 Hari" oneWeek: "1 Bulan" oneMonth: "satu bulan" +threeMonths: "3 bulan" +oneYear: "1 tahun" +threeDays: "3 hari" reflectMayTakeTime: "Mungkin perlu beberapa saat untuk dicerminkan." failedToFetchAccountInformation: "Gagal untuk mendapatkan informasi akun" rateLimitExceeded: "Batas sudah terlampaui" @@ -1101,6 +1108,7 @@ preservedUsernames: "Nama pengguna tercadangkan" preservedUsernamesDescription: "Daftar nama pengguna yang dicadangkan dipisah dengan baris baru. Nama pengguna berikut akan tidak dapat dipakai pada pembuatan akun normal, namun dapat digunakan oleh admin untuk membuat akun baru. Akun yang sudah ada dengan menggunakan nama pengguna ini tidak akan terpengaruh." createNoteFromTheFile: "Buat catatan dari berkas ini" archive: "Arsipkan" +archived: "Diarsipkan" channelArchiveConfirmTitle: "Yakin untuk mengarsipkan {name}?" channelArchiveConfirmDescription: "Kanal yang diarsipkan tidak akan muncul pada daftar kanal atau hasil pencarian. Postingan baru juga tidak dapat ditambahkan lagi." thisChannelArchived: "Kanal ini telah diarsipkan." @@ -1111,6 +1119,7 @@ preventAiLearning: "Tolak penggunaan Pembelajaran Mesin (AI Generatif)" preventAiLearningDescription: "Minta perayap web untuk tidak menggunakan materi teks atau gambar yang telah diposting ke dalam set data Pembelajaran Mesin (Prediktif / Generatif). Hal ini dicapai dengan menambahkan flag HTML-Response \"noai\" ke masing-masing konten. Pencegahan penuh mungkin tidak dapat dicapai dengan flag ini, karena juga dapat diabaikan begitu saja." options: "Opsi peran" specifyUser: "Pengguna spesifik" +openTagPageConfirm: "Apakah ingin membuka laman tagar?" failedToPreviewUrl: "Tidak dapat dipratinjau" update: "Perbarui" rolesThatCanBeUsedThisEmojiAsReaction: "Peran yang dapat menggunakan emoji ini sebagai reaksi" @@ -1243,6 +1252,18 @@ noDescription: "Tidak ada deskripsi" alwaysConfirmFollow: "Selalu konfirmasi ketika mengikuti" inquiry: "Hubungi kami" tryAgain: "Silahkan coba lagi." +createdLists: "Senarai yang dibuat" +createdAntennas: "Antena yang dibuat" +fromX: "Dari {x}" +noteOfThisUser: "Catatan oleh pengguna ini" +clipNoteLimitExceeded: "Klip ini tak bisa ditambahi lagi catatan." +performance: "Kinerja" +modified: "Diubah" +thereAreNChanges: "Ada {n} perubahan" +prohibitedWordsForNameOfUser: "Kata yang dilarang untuk nama pengguna" +_abuseUserReport: + accept: "Setuju" + reject: "Tolak" _delivery: status: "Status pengiriman" stop: "Ditangguhkan" @@ -1707,6 +1728,8 @@ _role: canSearchNotes: "Penggunaan pencarian catatan" canUseTranslator: "Penggunaan penerjemah" avatarDecorationLimit: "Jumlah maksimum dekorasi avatar yang dapat diterapkan" + canImportAntennas: "Izinkan mengimpor antena" + canImportUserLists: "Izinkan mengimpor senarai" _condition: roleAssignedTo: "Ditugaskan ke peran manual" isLocal: "Pengguna lokal" @@ -1943,6 +1966,7 @@ _soundSettings: driveFileTypeWarnDescription: "Pilih berkas audio" driveFileDurationWarn: "Audio ini terlalu panjang" driveFileDurationWarnDescription: "Audio panjang dapat mengganggu penggunaan Misskey. Masih ingin melanjutkan?" + driveFileError: "Tak bisa memuat audio. Mohon ubah pengaturan" _ago: future: "Masa depan" justNow: "Baru saja" @@ -2415,6 +2439,8 @@ _abuseReport: _notificationRecipient: _recipientType: mail: "Surel" + webhook: "Webhook" + keywords: "Kata kunci" _moderationLogTypes: createRole: "Peran telah dibuat" deleteRole: "Peran telah dihapus" @@ -2452,6 +2478,7 @@ _moderationLogTypes: deleteAvatarDecoration: "Hapus dekorasi avatar" unsetUserAvatar: "Hapus avatar pengguna" unsetUserBanner: "Hapus banner pengguna" + deleteAccount: "Akun dihapus" _fileViewer: title: "Rincian berkas" type: "Jenis berkas" diff --git a/locales/index.d.ts b/locales/index.d.ts index d2d72d1078..08fa10061b 100644 --- a/locales/index.d.ts +++ b/locales/index.d.ts @@ -1547,10 +1547,6 @@ export interface Locale extends ILocale { */ "registration": string; /** - * 誰ã§ã‚‚æ–°è¦ç™»éŒ²ã§ãるよã†ã«ã™ã‚‹ - */ - "enableRegistration": string; - /** * 招待 */ "invite": string; @@ -2367,6 +2363,10 @@ export interface Locale extends ILocale { */ "details": string; /** + * リノートã®è©³ç´° + */ + "renoteDetails": string; + /** * 絵文å—ã‚’é¸æŠž */ "chooseEmoji": string; @@ -3808,6 +3808,18 @@ export interface Locale extends ILocale { */ "oneMonth": string; /** + * 3ヶ月 + */ + "threeMonths": string; + /** + * 1å¹´ + */ + "oneYear": string; + /** + * 3æ—¥ + */ + "threeDays": string; + /** * åæ˜ ã•れるã¾ã§æ™‚é–“ãŒã‹ã‹ã‚‹å ´åˆãŒã‚りã¾ã™ã€‚ */ "reflectMayTakeTime": string; @@ -5191,6 +5203,72 @@ export interface Locale extends ILocale { * åå‰ã«ç¦æ¢ã•れã¦ã„ã‚‹æ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚ã“ã®åå‰ã‚’使用ã—ãŸã„å ´åˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。 */ "yourNameContainsProhibitedWordsDescription": string; + /** + * 投稿者ã«ã‚ˆã‚Šã€è¡¨ç¤ºã«ã¯ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã¨è¨å®šã•れã¦ã„ã¾ã™ + */ + "thisContentsAreMarkedAsSigninRequiredByAuthor": string; + /** + * ãƒãƒƒã‚¯ãƒ€ã‚¦ãƒ³ + */ + "lockdown": string; + /** + * ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„ + */ + "pleaseSelectAccount": string; + /** + * 利用å¯èƒ½ãªãƒãƒ¼ãƒ« + */ + "availableRoles": string; + /** + * 注æ„äº‹é …ã‚’ç†è§£ã—ãŸä¸Šã§ã‚ªãƒ³ã«ã—ã¾ã™ã€‚ + */ + "acknowledgeNotesAndEnable": string; + "_accountSettings": { + /** + * コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã™ã‚‹ + */ + "requireSigninToViewContents": string; + /** + * ã‚ãªãŸãŒä½œæˆã—ãŸå…¨ã¦ã®ãƒŽãƒ¼ãƒˆãªã©ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã™ã‚‹ã®ã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã—ã¾ã™ã€‚クãƒãƒ¼ãƒ©ãƒ¼ã«æƒ…å ±ãŒåŽé›†ã•れるã®ã‚’防ãåŠ¹æžœãŒæœŸå¾…ã§ãã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription1": string; + /** + * URLプレビュー(OGP)ã€Webページã¸ã®åŸ‹ã‚è¾¼ã¿ã€ãƒŽãƒ¼ãƒˆã®å¼•用ã«å¯¾å¿œã—ã¦ã„ãªã„サーãƒãƒ¼ã‹ã‚‰ã®è¡¨ç¤ºã‚‚ä¸å¯ã«ãªã‚Šã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription2": string; + /** + * リモートサーãƒãƒ¼ã«é€£åˆã•れãŸã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã§ã¯ã€ã“れらã®åˆ¶é™ãŒé©ç”¨ã•れãªã„å ´åˆãŒã‚りã¾ã™ã€‚ + */ + "requireSigninToViewContentsDescription3": string; + /** + * éŽåŽ»ã®ãƒŽãƒ¼ãƒˆã‚’フォãƒãƒ¯ãƒ¼ã®ã¿è¡¨ç¤ºå¯èƒ½ã«ã™ã‚‹ + */ + "makeNotesFollowersOnlyBefore": string; + /** + * ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã„ã‚‹é–“ã€è¨å®šã•ã‚ŒãŸæ—¥æ™‚よりéŽåŽ»ã€ã¾ãŸã¯è¨å®šã•ã‚ŒãŸæ™‚間を経éŽã—ã¦ã„るノートãŒãƒ•ã‚©ãƒãƒ¯ãƒ¼ã®ã¿è¡¨ç¤ºå¯èƒ½ã«ãªã‚Šã¾ã™ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚å…ƒã«æˆ»ã‚Šã¾ã™ã€‚ + */ + "makeNotesFollowersOnlyBeforeDescription": string; + /** + * éŽåŽ»ã®ãƒŽãƒ¼ãƒˆã‚’éžå…¬é–‹åŒ–ã™ã‚‹ + */ + "makeNotesHiddenBefore": string; + /** + * ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã„ã‚‹é–“ã€è¨å®šã•ã‚ŒãŸæ—¥æ™‚よりéŽåŽ»ã€ã¾ãŸã¯è¨å®šã•ã‚ŒãŸæ™‚間を経éŽã—ã¦ã„るノートãŒè‡ªåˆ†ã®ã¿è¡¨ç¤ºå¯èƒ½(éžå…¬é–‹åŒ–)ã«ãªã‚Šã¾ã™ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚å…ƒã«æˆ»ã‚Šã¾ã™ã€‚ + */ + "makeNotesHiddenBeforeDescription": string; + /** + * リモートサーãƒãƒ¼ã«é€£åˆã•れãŸãƒŽãƒ¼ãƒˆã«ã¯åŠ¹æžœãŒåŠã°ãªã„å ´åˆãŒã‚りã¾ã™ã€‚ + */ + "mayNotEffectForFederatedNotes": string; + /** + * 指定ã—ãŸæ™‚間を経éŽã—ã¦ã„るノート + */ + "notesHavePassedSpecifiedPeriod": string; + /** + * 指定ã—ãŸæ—¥æ™‚よりå‰ã®ãƒŽãƒ¼ãƒˆ + */ + "notesOlderThanSpecifiedDateAndTime": string; + }; "_abuseUserReport": { /** * è»¢é€ @@ -5734,6 +5812,14 @@ export interface Locale extends ILocale { */ "inquiryUrlDescription": string; /** + * アカウントã®ä½œæˆã‚’オープンã«ã™ã‚‹ + */ + "openRegistration": string; + /** + * 登録を開放ã™ã‚‹ã“ã¨ã¯ãƒªã‚¹ã‚¯ãŒä¼´ã„ã¾ã™ã€‚サーãƒãƒ¼ã‚’常ã«ç›£è¦–ã—ã€ãƒˆãƒ©ãƒ–ルãŒç™ºç”Ÿã—ãŸéš›ã«ã™ãã«å¯¾å¿œã§ãる体制ãŒã‚ã‚‹å ´åˆã®ã¿ã‚ªãƒ³ã«ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚ + */ + "openRegistrationWarning": string; + /** * 一定期間モデレーターã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ãƒ†ã‚£ãŒæ¤œå‡ºã•れãªã‹ã£ãŸå ´åˆã€ã‚¹ãƒ‘ム防æ¢ã®ãŸã‚ã“ã®è¨å®šã¯è‡ªå‹•ã§ã‚ªãƒ•ã«ãªã‚Šã¾ã™ã€‚ */ "thisSettingWillAutomaticallyOffWhenModeratorsInactive": string; @@ -8459,14 +8545,26 @@ export interface Locale extends ILocale { */ "callback": string; /** + * アクセスを許å¯ã—ã¾ã—㟠+ */ + "accepted": string; + /** * アクセスを拒å¦ã—ã¾ã—㟠*/ "denied": string; /** + * 以下ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ã—ã¦æ“作ã—ã¦ã„ã¾ã™ + */ + "scopeUser": string; + /** * アプリケーションã«ã‚¢ã‚¯ã‚»ã‚¹è¨±å¯ã‚’与ãˆã‚‹ã«ã¯ã€ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã§ã™ã€‚ */ "pleaseLogin": string; /** + * アクセスを許å¯ã™ã‚‹ã¨ã€è‡ªå‹•ã§ä»¥ä¸‹ã®URLã«é·ç§»ã—ã¾ã™ + */ + "byClickingYouWillBeRedirectedToThisUrl": string; + /** * Allowed */ "allowed": string; @@ -10623,6 +10721,38 @@ export interface Locale extends ILocale { */ "codeGeneratedDescription": string; }; + "_selfXssPrevention": { + /** + * è¦å‘Š + */ + "warning": string; + /** + * 「ã“ã®ç”»é¢ã«ä½•ã‹è²¼ã‚Šä»˜ã‘ã‚ã€ã¯ã™ã¹ã¦è©æ¬ºã§ã™ã€‚ + */ + "title": string; + /** + * ã“ã“ã«ä½•ã‹ã‚’貼り付ã‘ã‚‹ã¨ã€æ‚ªæ„ã®ã‚るユーザーã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ä¹—ã£å–られãŸã‚Šã€å€‹äººæƒ…å ±ã‚’ç›—ã¾ã‚ŒãŸã‚Šã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚ + */ + "description1": string; + /** + * 貼り付ã‘よã†ã¨ã—ã¦ã„ã‚‹ã‚‚ã®ãŒä½•ãªã®ã‹ã‚’æ£ç¢ºã«ç†è§£ã—ã¦ã„ãªã„å ´åˆã¯ã€%c今ã™ã作æ¥ã‚’䏿¢ã—ã¦ã“ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã‚’é–‰ã˜ã¦ãã ã•ã„。 + */ + "description2": string; + /** + * 詳ã—ãã¯ã“ã¡ã‚‰ã‚’ã”確èªãã ã•ã„。 {link} + */ + "description3": ParameterizedString<"link">; + }; + "_followRequest": { + /** + * å—ã‘å–ã£ãŸç”³è«‹ + */ + "recieved": string; + /** + * é€ã£ãŸç”³è«‹ + */ + "sent": string; + }; /** * Approvals */ diff --git a/locales/index.js b/locales/index.js index b08158e55d..a9f81da1cf 100644 --- a/locales/index.js +++ b/locales/index.js @@ -15,6 +15,7 @@ export const merge = (...args) => args.reduce((a, c) => ({ const languages = [ 'ar-SA', + 'ca-ES', 'cs-CZ', 'da-DK', 'de-DE', diff --git a/locales/it-IT.yml b/locales/it-IT.yml index bcabf1bdb6..66ca935b1b 100644 --- a/locales/it-IT.yml +++ b/locales/it-IT.yml @@ -68,7 +68,7 @@ reply: "Rispondi" loadMore: "Mostra di più" showMore: "Espandi" showLess: "Comprimi" -youGotNewFollower: "Adesso ti segue" +youGotNewFollower: "Hai un nuovo Follower" receiveFollowRequest: "Hai ricevuto una richiesta di follow" followRequestAccepted: "Ha accettato la tua richiesta di follow" mention: "Menzioni" @@ -80,14 +80,14 @@ export: "Esporta" files: "Allegati" download: "Scarica" driveFileDeleteConfirm: "Vuoi davvero eliminare il file \"{name}\", e le Note a cui è stato allegato?" -unfollowConfirm: "Vuoi davvero smettere di seguire {name}?" +unfollowConfirm: "Vuoi davvero togliere il Following a {name}?" exportRequested: "Hai richiesto un'esportazione, e potrebbe volerci tempo. Quando sarà compiuta, il file verrà aggiunto direttamente al Drive." importRequested: "Hai richiesto un'importazione. Potrebbe richiedere un po' di tempo." lists: "Liste" noLists: "Nessuna lista" note: "Nota" notes: "Note" -following: "Follow" +following: "Following" followers: "Follower" followsYou: "Follower" createList: "Aggiungi una nuova lista" @@ -106,7 +106,7 @@ defaultNoteVisibility: "Privacy predefinita delle note" follow: "Segui" followRequest: "Richiesta di follow" followRequests: "Richieste di follow" -unfollow: "Smetti di seguire" +unfollow: "Togli Following" followRequestPending: "Richiesta in approvazione" enterEmoji: "Inserisci emoji" renote: "Rinota" @@ -195,7 +195,7 @@ setWallpaper: "Imposta sfondo" removeWallpaper: "Elimina lo sfondo" searchWith: "Cerca: {q}" youHaveNoLists: "Non hai ancora creato nessuna lista" -followConfirm: "Vuoi seguire {name}?" +followConfirm: "Confermi il Following a {name}?" proxyAccount: "Profilo proxy" proxyAccountDescription: "Un profilo proxy funziona come follower per i profili remoti, sotto certe condizioni. Ad esempio, quando un profilo locale ne inserisce uno remoto in una lista (senza seguirlo), se nessun altro segue quel profilo remoto, le attività non possono essere distribuite. Dunque, il profilo proxy le seguirà per tutti." host: "Host" @@ -263,7 +263,7 @@ all: "Tutte" subscribing: "Iscrizione" publishing: "Pubblicazione" notResponding: "Nessuna risposta" -instanceFollowing: "Seguiti dall'istanza" +instanceFollowing: "Istanza Following" instanceFollowers: "Follower dell'istanza" instanceUsers: "Profili nell'istanza" changePassword: "Aggiorna Password" @@ -382,7 +382,6 @@ enableLocalTimeline: "Abilita la timeline locale" enableGlobalTimeline: "Abilita la timeline federata" disablingTimelinesInfo: "Anche disabilitandole, gli Amministratori e i Moderatori potranno comunque accedervi." registration: "Iscriviti" -enableRegistration: "Consenti a chiunque di registrarsi" invite: "Invita" driveCapacityPerLocalAccount: "Capienza del Drive per profilo locale" driveCapacityPerRemoteAccount: "Capienza del Drive per profilo remoto" @@ -615,7 +614,7 @@ unsetUserBannerConfirm: "Vuoi davvero rimuovere l'intestazione dal profilo?" deleteAllFiles: "Elimina tutti i file" deleteAllFilesConfirm: "Vuoi davvero eliminare tutti i file?" removeAllFollowing: "Annulla tutti i follow" -removeAllFollowingDescription: "Cancella tutti i follows del server {host}. Per favore, esegui se, ad esempio, l'istanza non esiste più." +removeAllFollowingDescription: "Togli il Following a tutti i profili su {host}. Utile, ad esempio, quando l'istanza non esiste più." userSuspended: "L'utente è in sospensione" userSilenced: "Profilo silenziato" yourAccountSuspendedTitle: "Questo profilo è sospeso" @@ -688,7 +687,7 @@ hardWordMute: "Filtro parole forte" regexpError: "errore regex" regexpErrorDescription: "Si è verificato un errore nell'espressione regolare alla riga {line} della parola muta {tab}:" instanceMute: "Silenziare l'istanza" -userSaysSomething: "{name} ha parlato" +userSaysSomething: "{name} ha detto qualcosa" makeActive: "Attiva" display: "Visualizza" copy: "Copia" @@ -703,7 +702,7 @@ notificationSetting: "Impostazioni notifiche" notificationSettingDesc: "Seleziona il tipo di notifiche da visualizzare." useGlobalSetting: "Usa impostazioni generali" useGlobalSettingDesc: "Quando attiva, verranno utilizzate le impostazioni notifiche del profilo. Altrimenti si possono segliere impostazioni personalizzate." -other: "Ulteriori" +other: "Eccetera" regenerateLoginToken: "Genera di nuovo un token di connessione" regenerateLoginTokenDescription: "Genera un nuovo token di autenticazione. Solitamente questa operazione non è necessaria: quando si genera un nuovo token, tutti i dispositivi vanno disconnessi." theKeywordWhenSearchingForCustomEmoji: "Questa sarà la parola chiave durante la ricerca di emoji personalizzate" @@ -747,7 +746,7 @@ repliesCount: "Numero di risposte inviate" renotesCount: "Numero di note che hai ricondiviso" repliedCount: "Numero di risposte ricevute" renotedCount: "Numero delle tue note ricondivise" -followingCount: "Numero di profili seguiti" +followingCount: "Numero di Following" followersCount: "Numero di profili che ti seguono" sentReactionsCount: "Numero di reazioni inviate" receivedReactionsCount: "Numero di reazioni ricevute" @@ -901,8 +900,8 @@ pubSub: "Publish/Subscribe del profilo" lastCommunication: "La comunicazione più recente" resolved: "Risolto" unresolved: "Non risolto" -breakFollow: "Impedire di seguirmi" -breakFollowConfirm: "Vuoi davvero che questo profilo smetta di seguirti?" +breakFollow: "Rimuovi Follower" +breakFollowConfirm: "Vuoi davvero togliere questo Follower?" itsOn: "Abilitato" itsOff: "Disabilitato" on: "Acceso" @@ -917,7 +916,7 @@ makeReactionsPublicDescription: "La lista delle reazioni che avete fatto è a di classic: "Classico" muteThread: "Silenziare conversazione" unmuteThread: "Riattiva la conversazione" -followingVisibility: "Visibilità dei profili seguiti" +followingVisibility: "Visibilità dei Following" followersVisibility: "Visibilità dei profili che ti seguono" continueThread: "Altre conversazioni" deleteAccountConfirm: "Così verrà eliminato il profilo. Vuoi procedere?" @@ -947,6 +946,9 @@ oneHour: "1 ora" oneDay: "1 giorno" oneWeek: "1 settimana" oneMonth: "Un mese" +threeMonths: "3 mesi" +oneYear: "1 anno" +threeDays: "3 giorni" reflectMayTakeTime: "Potrebbe essere necessario un po' di tempo perché ciò abbia effetto." failedToFetchAccountInformation: "Impossibile recuperare le informazioni sul profilo" rateLimitExceeded: "Superato il limite di richieste." @@ -965,7 +967,7 @@ driveCapOverrideLabel: "Modificare la capienza del Drive per questo profilo" driveCapOverrideCaption: "Se viene specificato meno di 0, viene annullato." requireAdminForView: "Per visualizzarli, è necessario aver effettuato l'accesso con un profilo amministratore." isSystemAccount: "Questi profili vengono creati e gestiti automaticamente dal sistema" -typeToConfirm: "Per eseguire questa operazione, digitare {x}" +typeToConfirm: "Digita {x} per continuare" deleteAccount: "Eliminazione profilo" document: "Documento" numberOfPageCache: "Numero di pagine cache" @@ -1020,7 +1022,7 @@ neverShow: "Non mostrare più" remindMeLater: "Rimanda" didYouLikeMisskey: "Ti piace Misskey?" pleaseDonate: "Misskey è il software libero utilizzato su {host}. Offrendo una donazione è più facile continuare a svilupparlo!" -correspondingSourceIsAvailable: "" +correspondingSourceIsAvailable: "Il codice sorgente corrispondente è disponibile su {anchor}." roles: "Ruoli" role: "Ruolo" noRole: "Ruolo non trovato" @@ -1130,7 +1132,7 @@ channelArchiveConfirmDescription: "Un canale archiviato non compare nell'elenco thisChannelArchived: "Questo canale è stato archiviato." displayOfNote: "Visualizzazione delle Note" initialAccountSetting: "Impostazioni iniziali del profilo" -youFollowing: "Seguiti" +youFollowing: "Following" preventAiLearning: "Impedisci l'apprendimento della IA" preventAiLearningDescription: "Aggiungendo il campo \"noai\" alla risposta HTML, si indica ai Robot esterni di non usare testi e allegati per addestrare sistemi di Machine Learning (IA predittiva/generativa). Anche se è impossibile sapere se la richiesta venga onorata o semplicemente ignorata." options: "Opzioni del ruolo" @@ -1293,6 +1295,21 @@ prohibitedWordsForNameOfUser: "Parole proibite (nome utente)" prohibitedWordsForNameOfUserDescription: "Il sistema rifiuta di rinominare un utente, se il nome contiene qualsiasi parola nell'elenco. Sono esenti i profili con privilegi di moderazione." yourNameContainsProhibitedWords: "Il nome che hai scelto contiene una o più parole vietate" yourNameContainsProhibitedWordsDescription: "Se desideri comunque utilizzare questo nome, contatta l''amministrazione." +thisContentsAreMarkedAsSigninRequiredByAuthor: "L'autore richiede di iscriversi per vedere il contenuto" +lockdown: "Isolamento" +pleaseSelectAccount: "Per favore, seleziona un profilo" +_accountSettings: + requireSigninToViewContents: "Per vedere il contenuto, è necessaria l'iscrizione" + requireSigninToViewContentsDescription1: "Richiedere l'iscrizione per visualizzare tutte le Note e gli altri contenuti che hai creato. Probabilmente l'effetto è impedire la raccolta di informazioni da parte dei bot crawler." + requireSigninToViewContentsDescription2: "La visualizzazione verrà disabilitata a server che non supportano l'anteprima URL (OGP), all'incorporamento nelle pagine Web e alla citazione delle Note." + requireSigninToViewContentsDescription3: "Queste restrizioni potrebbero non applicarsi al contenuto federato su server remoti." + makeNotesFollowersOnlyBefore: "Rendi visibili solo ai Follower le Note pubblicate in precedenza" + makeNotesFollowersOnlyBeforeDescription: "Mentre questa funzione è abilitata, le Note antecedenti al momento impostato, saranno visibili solo ai profili Follower. Disabilitandola nuovamente, verrà ripristinata anche la visibilità pubblica della Nota." + makeNotesHiddenBefore: "Nascondi le Note pubblicate in precedenza" + makeNotesHiddenBeforeDescription: "Mentre questa funzione è abilitata, le Note antecedenti al momento impostato, saranno visibili soltanto a te (private). Disabilitandola nuovamente, verrà ripristinata anche la visibilità pubblica della Nota." + mayNotEffectForFederatedNotes: "Le Note già federate su server remoti potrebbero non essere modificate." + notesHavePassedSpecifiedPeriod: "Note antecedenti al periodo specificato" + notesOlderThanSpecifiedDateAndTime: "Note antecedenti al momento specificato" _abuseUserReport: forward: "Inoltra" forwardDescription: "Inoltra il report al server remoto, per mezzo di account di sistema, anonimo." @@ -1378,7 +1395,7 @@ _initialTutorial: _timeline: title: "Come funziona la Timeline" description1: "Misskey fornisce alcune Timeline (sequenze cronologiche di Note). Una di queste potrebbe essere stata disattivata dagli amministratori." - home: "le Note provenienti dai profili che segui (follow)." + home: "le Note provenienti dai profili che segui (Following)." local: "tutte le Note pubblicate dai profili di questa istanza." social: "sia le Note della Timeline Home che quelle della Timeline Locale, insieme!" global: "le Note da pubblicate da tutte le altre istanze federate con la nostra." @@ -1416,7 +1433,7 @@ _initialTutorial: title: "Il tutorial è finito! 🎉" description: "Queste sono solamente alcune delle funzionalità principali di Misskey. Per ulteriori informazioni, {link}." _timelineDescription: - home: "Nella Timeline Home, la tua cronologia principale, puoi vedere le Note provenienti dai profili che segui (follow)." + home: "Nella Timeline Home, la tua cronologia principale, puoi vedere le Note provenienti dai profili che segui (Following)." local: "La Timeline Locale, è una cronologia di Note pubblicate da tutti i profili iscritti su questo server." social: "La Timeline Sociale, unisce in ordine cronologico l'elenco di Note presenti nella Timeline Home e quella Locale." global: "La Timeline Federata ti consente di vedere le Note pubblicate dai profili di tutti gli altri server federati a questo." @@ -1442,7 +1459,7 @@ _accountMigration: moveFrom: "Migra un altro profilo dentro a questo" moveFromSub: "Crea un alias verso un altro profilo remoto" moveFromLabel: "Profilo da cui migrare #{n}" - moveFromDescription: "Se desideri spostare i profili follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività ! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it" + moveFromDescription: "Se desideri spostare i Follower da un altro profilo a questo, devi prima creare un alias qui. Assicurati averlo creato PRIMA di eseguire l'attività ! Inserisci l'indirizzo del profilo mittente in questo modo: @persona@istanza.it" moveTo: "Migrare questo profilo verso un un altro" moveToLabel: "Profilo verso cui migrare" moveCannotBeUndone: "La migrazione è irreversibile, non può essere interrotta o annullata." @@ -1451,7 +1468,7 @@ _accountMigration: startMigration: "Avvia la migrazione" migrationConfirm: "Vuoi davvero migrare questo profilo su {account}? L'azione è irreversibile e non potrai più utilizzare questo profilo nel suo stato originale.\nInoltre, assicurati di aver già creato un alias sull'account a cui ti stai trasferendo." movedAndCannotBeUndone: "Il tuo profilo è stato migrato.\nLa migrazione non può essere annullata." - postMigrationNote: "Questo profilo smetterà di seguire gli altri profili remoti a 24 ore dal termine della migrazione.\nSia i Follow che i Follower scenderanno a zero. I tuoi follower saranno comunque in grado di vedere le Note per soli follower, poiché non smetteranno di seguirti." + postMigrationNote: "Questo profilo smetterà di seguire gli altri profili remoti a 24 ore dal termine della migrazione.\nSia i Following che i Follower scenderanno a zero. I tuoi Follower saranno comunque in grado di vedere le Note per soli Follower, poiché non smetteranno di seguirti." movedTo: "Profilo verso cui migrare" _achievements: earnedAt: "Data di conseguimento" @@ -1844,7 +1861,7 @@ _gallery: unlike: "Non mi piace più" _email: _follow: - title: "Adesso ti segue" + title: "Follower aggiuntivo" _receiveFollowRequest: title: "Hai ricevuto una richiesta di follow" _plugin: @@ -1908,7 +1925,7 @@ _channel: removeBanner: "Rimuovi intestazione" featured: "Di tendenza" owned: "I miei canali" - following: "Seguiti" + following: "Following" usersCount: "{n} partecipanti" notesCount: "{n} note" nameAndDescription: "Nome e descrizione" @@ -2074,7 +2091,7 @@ _permissions: "read:favorites": "Visualizza i tuoi preferiti" "write:favorites": "Gestisci i tuoi preferiti" "read:following": "Vedi le informazioni di follow" - "write:following": "Following di altri profili" + "write:following": "Aggiungere e togliere Following" "read:messaging": "Visualizzare la chat" "write:messaging": "Gestire la chat" "read:mutes": "Vedi i profili silenziati" @@ -2157,11 +2174,14 @@ _auth: permissionAsk: "Questa app richiede le seguenti autorizzazioni:" pleaseGoBack: "Si prega di ritornare sulla app" callback: "Ritornando sulla app" + accepted: "Accesso concesso" denied: "Accesso negato" + scopeUser: "Sto funzionando per il seguente profilo" pleaseLogin: "Per favore accedi al tuo account per cambiare i permessi dell'applicazione" + byClickingYouWillBeRedirectedToThisUrl: "Consentendo l'accesso, si verrà reindirizzati presso questo indirizzo URL" _antennaSources: all: "Tutte le note" - homeTimeline: "Note dagli utenti che segui" + homeTimeline: "Note dai tuoi Following" users: "Note dagli utenti selezionati" userList: "Note dagli utenti della lista selezionata" userBlacklist: "Tutte le Note tranne quelle di uno o più profili specificati" @@ -2274,7 +2294,7 @@ _exportOrImport: allNotes: "Tutte le note" favoritedNotes: "Note preferite" clips: "Clip" - followingList: "Follow" + followingList: "Following" muteList: "Elenco profili silenziati" blockingList: "Elenco profili bloccati" userLists: "Liste" @@ -2390,7 +2410,7 @@ _notification: youGotReply: "{name} ti ha risposto" youGotQuote: "{name} ha citato la tua Nota e ha detto" youRenoted: "{name} ha rinotato" - youWereFollowed: "Adesso ti segue" + youWereFollowed: "Follower aggiuntivo" youReceivedFollowRequest: "Hai ricevuto una richiesta di follow" yourFollowRequestAccepted: "La tua richiesta di follow è stata accettata" pollEnded: "Risultati del sondaggio." @@ -2413,7 +2433,7 @@ _notification: _types: all: "Tutto" note: "Nuove Note" - follow: "Nuovi profili follower" + follow: "Follower" mention: "Menzioni" reply: "Risposte" renote: "Rinota" @@ -2429,7 +2449,7 @@ _notification: test: "Prova la notifica" app: "Notifiche da applicazioni" _actions: - followBack: "Segui" + followBack: "Following ricambiato" reply: "Rispondi" renote: "Rinota" _deck: @@ -2481,7 +2501,7 @@ _webhookSettings: trigger: "Trigger" active: "Attivo" _events: - follow: "Quando segui un profilo" + follow: "Quando aggiungi Following" followed: "Quando ti segue un profilo" note: "Quando pubblichi una Nota" reply: "Quando rispondono ad una Nota" @@ -2710,3 +2730,9 @@ _embedCodeGen: generateCode: "Crea il codice di incorporamento" codeGenerated: "Codice generato" codeGeneratedDescription: "Incolla il codice appena generato sul tuo sito web." +_selfXssPrevention: + warning: "Avviso" + title: "\"Incolla qualcosa su questa schermata\" è tutta una truffa." + description1: "Incollando qualcosa qui, malintenzionati potrebbero prendere il controllo del tuo profilo o rubare i tuoi dati personali." + description2: "Se non sai esattamente cosa stai facendo, %c smetti subito e chiudi questa finestra." + description3: "Per favore, controlla questo collegamento per avere maggiori dettagli. {link}" diff --git a/locales/ja-JP.yml b/locales/ja-JP.yml index c448d4d50a..1b59708d85 100644 --- a/locales/ja-JP.yml +++ b/locales/ja-JP.yml @@ -382,7 +382,6 @@ enableLocalTimeline: "ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインを有効ã«ã™ã‚‹" enableGlobalTimeline: "ã‚°ãƒãƒ¼ãƒãƒ«ã‚¿ã‚¤ãƒ ラインを有効ã«ã™ã‚‹" disablingTimelinesInfo: "ã“れらã®ã‚¿ã‚¤ãƒ ラインを無効化ã—ã¦ã‚‚ã€åˆ©ä¾¿æ€§ã®ãŸã‚管ç†è€…ãŠã‚ˆã³ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯å¼•ãç¶šã利用ã™ã‚‹ã“ã¨ãŒã§ãã¾ã™ã€‚" registration: "登録" -enableRegistration: "誰ã§ã‚‚æ–°è¦ç™»éŒ²ã§ãるよã†ã«ã™ã‚‹" invite: "招待" driveCapacityPerLocalAccount: "ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã²ã¨ã‚Šã‚ãŸã‚Šã®ãƒ‰ãƒ©ã‚¤ãƒ–容é‡" driveCapacityPerRemoteAccount: "リモートユーザーã²ã¨ã‚Šã‚ãŸã‚Šã®ãƒ‰ãƒ©ã‚¤ãƒ–容é‡" @@ -587,6 +586,7 @@ masterVolume: "マスター音é‡" notUseSound: "サウンドを出力ã—ãªã„" useSoundOnlyWhenActive: "MisskeyãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªæ™‚ã®ã¿ã‚µã‚¦ãƒ³ãƒ‰ã‚’出力ã™ã‚‹" details: "詳細" +renoteDetails: "リノートã®è©³ç´°" chooseEmoji: "絵文å—ã‚’é¸æŠž" unableToProcess: "æ“作を完了ã§ãã¾ã›ã‚“" recentUsed: "最近使用" @@ -947,6 +947,9 @@ oneHour: "1時間" oneDay: "1æ—¥" oneWeek: "1週間" oneMonth: "1ヶ月" +threeMonths: "3ヶ月" +oneYear: "1å¹´" +threeDays: "3æ—¥" reflectMayTakeTime: "åæ˜ ã•れるã¾ã§æ™‚é–“ãŒã‹ã‹ã‚‹å ´åˆãŒã‚りã¾ã™ã€‚" failedToFetchAccountInformation: "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆæƒ…å ±ã®å–å¾—ã«å¤±æ•—ã—ã¾ã—ãŸ" rateLimitExceeded: "レート制é™ã‚’è¶…ãˆã¾ã—ãŸ" @@ -1293,6 +1296,24 @@ prohibitedWordsForNameOfUser: "ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ï¼ˆãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ï¼‰" prohibitedWordsForNameOfUserDescription: "ã“ã®ãƒªã‚¹ãƒˆã«å«ã¾ã‚Œã‚‹æ–‡å—列ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ã«å«ã¾ã‚Œã‚‹å ´åˆã€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®åå‰ã®å¤‰æ›´ã‚’æ‹’å¦ã—ã¾ã™ã€‚モデレーター権é™ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã“ã®åˆ¶é™ã®å½±éŸ¿ã‚’å—ã‘ã¾ã›ã‚“。" yourNameContainsProhibitedWords: "変更ã—よã†ã¨ã—ãŸåå‰ã«ç¦æ¢ã•ã‚ŒãŸæ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™" yourNameContainsProhibitedWordsDescription: "åå‰ã«ç¦æ¢ã•れã¦ã„ã‚‹æ–‡å—列ãŒå«ã¾ã‚Œã¦ã„ã¾ã™ã€‚ã“ã®åå‰ã‚’使用ã—ãŸã„å ´åˆã¯ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«ãŠå•ã„åˆã‚ã›ãã ã•ã„。" +thisContentsAreMarkedAsSigninRequiredByAuthor: "投稿者ã«ã‚ˆã‚Šã€è¡¨ç¤ºã«ã¯ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã¨è¨å®šã•れã¦ã„ã¾ã™" +lockdown: "ãƒãƒƒã‚¯ãƒ€ã‚¦ãƒ³" +pleaseSelectAccount: "ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’é¸æŠžã—ã¦ãã ã•ã„" +availableRoles: "利用å¯èƒ½ãªãƒãƒ¼ãƒ«" +acknowledgeNotesAndEnable: "注æ„äº‹é …ã‚’ç†è§£ã—ãŸä¸Šã§ã‚ªãƒ³ã«ã—ã¾ã™ã€‚" + +_accountSettings: + requireSigninToViewContents: "コンテンツã®è¡¨ç¤ºã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã™ã‚‹" + requireSigninToViewContentsDescription1: "ã‚ãªãŸãŒä½œæˆã—ãŸå…¨ã¦ã®ãƒŽãƒ¼ãƒˆãªã©ã®ã‚³ãƒ³ãƒ†ãƒ³ãƒ„を表示ã™ã‚‹ã®ã«ãƒã‚°ã‚¤ãƒ³ã‚’å¿…é ˆã«ã—ã¾ã™ã€‚クãƒãƒ¼ãƒ©ãƒ¼ã«æƒ…å ±ãŒåŽé›†ã•れるã®ã‚’防ãåŠ¹æžœãŒæœŸå¾…ã§ãã¾ã™ã€‚" + requireSigninToViewContentsDescription2: "URLプレビュー(OGP)ã€Webページã¸ã®åŸ‹ã‚è¾¼ã¿ã€ãƒŽãƒ¼ãƒˆã®å¼•用ã«å¯¾å¿œã—ã¦ã„ãªã„サーãƒãƒ¼ã‹ã‚‰ã®è¡¨ç¤ºã‚‚ä¸å¯ã«ãªã‚Šã¾ã™ã€‚" + requireSigninToViewContentsDescription3: "リモートサーãƒãƒ¼ã«é€£åˆã•れãŸã‚³ãƒ³ãƒ†ãƒ³ãƒ„ã§ã¯ã€ã“れらã®åˆ¶é™ãŒé©ç”¨ã•れãªã„å ´åˆãŒã‚りã¾ã™ã€‚" + makeNotesFollowersOnlyBefore: "éŽåŽ»ã®ãƒŽãƒ¼ãƒˆã‚’フォãƒãƒ¯ãƒ¼ã®ã¿è¡¨ç¤ºå¯èƒ½ã«ã™ã‚‹" + makeNotesFollowersOnlyBeforeDescription: "ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã„ã‚‹é–“ã€è¨å®šã•ã‚ŒãŸæ—¥æ™‚よりéŽåŽ»ã€ã¾ãŸã¯è¨å®šã•ã‚ŒãŸæ™‚間を経éŽã—ã¦ã„るノートãŒãƒ•ã‚©ãƒãƒ¯ãƒ¼ã®ã¿è¡¨ç¤ºå¯èƒ½ã«ãªã‚Šã¾ã™ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚å…ƒã«æˆ»ã‚Šã¾ã™ã€‚" + makeNotesHiddenBefore: "éŽåŽ»ã®ãƒŽãƒ¼ãƒˆã‚’éžå…¬é–‹åŒ–ã™ã‚‹" + makeNotesHiddenBeforeDescription: "ã“ã®æ©Ÿèƒ½ãŒæœ‰åйã«ãªã£ã¦ã„ã‚‹é–“ã€è¨å®šã•ã‚ŒãŸæ—¥æ™‚よりéŽåŽ»ã€ã¾ãŸã¯è¨å®šã•ã‚ŒãŸæ™‚間を経éŽã—ã¦ã„るノートãŒè‡ªåˆ†ã®ã¿è¡¨ç¤ºå¯èƒ½(éžå…¬é–‹åŒ–)ã«ãªã‚Šã¾ã™ã€‚ç„¡åŠ¹ã«æˆ»ã™ã¨ã€ãƒŽãƒ¼ãƒˆã®å…¬é–‹çŠ¶æ…‹ã‚‚å…ƒã«æˆ»ã‚Šã¾ã™ã€‚" + mayNotEffectForFederatedNotes: "リモートサーãƒãƒ¼ã«é€£åˆã•れãŸãƒŽãƒ¼ãƒˆã«ã¯åŠ¹æžœãŒåŠã°ãªã„å ´åˆãŒã‚りã¾ã™ã€‚" + notesHavePassedSpecifiedPeriod: "指定ã—ãŸæ™‚間を経éŽã—ã¦ã„るノート" + notesOlderThanSpecifiedDateAndTime: "指定ã—ãŸæ—¥æ™‚よりå‰ã®ãƒŽãƒ¼ãƒˆ" _abuseUserReport: forward: "転é€" @@ -1446,6 +1467,8 @@ _serverSettings: reactionsBufferingDescription: "有効ã«ã™ã‚‹ã¨ã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä½œæˆæ™‚ã®ãƒ‘フォーマンスãŒå¤§å¹…ã«å‘上ã—ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ã‚’軽減ã™ã‚‹ã“ã¨ãŒå¯èƒ½ã§ã™ã€‚ãŸã ã—ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨é‡ã¯å¢—åŠ ã—ã¾ã™ã€‚" inquiryUrl: "å•ã„åˆã‚ã›å…ˆURL" inquiryUrlDescription: "サーãƒãƒ¼é‹å–¶è€…ã¸ã®ãŠå•ã„åˆã‚ã›ãƒ•ォームã®URLã‚„ã€é‹å–¶è€…ã®é€£çµ¡å…ˆç‰ãŒè¨˜è¼‰ã•れãŸWebページã®URLを指定ã—ã¾ã™ã€‚" + openRegistration: "アカウントã®ä½œæˆã‚’オープンã«ã™ã‚‹" + openRegistrationWarning: "登録を開放ã™ã‚‹ã“ã¨ã¯ãƒªã‚¹ã‚¯ãŒä¼´ã„ã¾ã™ã€‚サーãƒãƒ¼ã‚’常ã«ç›£è¦–ã—ã€ãƒˆãƒ©ãƒ–ルãŒç™ºç”Ÿã—ãŸéš›ã«ã™ãã«å¯¾å¿œã§ãる体制ãŒã‚ã‚‹å ´åˆã®ã¿ã‚ªãƒ³ã«ã™ã‚‹ã“ã¨ã‚’推奨ã—ã¾ã™ã€‚" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ãƒ†ã‚£ãŒæ¤œå‡ºã•れãªã‹ã£ãŸå ´åˆã€ã‚¹ãƒ‘ム防æ¢ã®ãŸã‚ã“ã®è¨å®šã¯è‡ªå‹•ã§ã‚ªãƒ•ã«ãªã‚Šã¾ã™ã€‚" _accountMigration: @@ -2199,8 +2222,11 @@ _auth: permissionAsk: "ã“ã®ã‚¢ãƒ—ãƒªã¯æ¬¡ã®æ¨©é™ã‚’è¦æ±‚ã—ã¦ã„ã¾ã™" pleaseGoBack: "ã‚¢ãƒ—ãƒªã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã«æˆ»ã£ã¦ã‚„ã£ã¦ã„ã£ã¦ãã ã•ã„" callback: "ã‚¢ãƒ—ãƒªã‚±ãƒ¼ã‚·ãƒ§ãƒ³ã«æˆ»ã£ã¦ã„ã¾ã™" + accepted: "アクセスを許å¯ã—ã¾ã—ãŸ" denied: "アクセスを拒å¦ã—ã¾ã—ãŸ" + scopeUser: "以下ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¨ã—ã¦æ“作ã—ã¦ã„ã¾ã™" pleaseLogin: "アプリケーションã«ã‚¢ã‚¯ã‚»ã‚¹è¨±å¯ã‚’与ãˆã‚‹ã«ã¯ã€ãƒã‚°ã‚¤ãƒ³ãŒå¿…è¦ã§ã™ã€‚" + byClickingYouWillBeRedirectedToThisUrl: "アクセスを許å¯ã™ã‚‹ã¨ã€è‡ªå‹•ã§ä»¥ä¸‹ã®URLã«é·ç§»ã—ã¾ã™" _antennaSources: all: "å…¨ã¦ã®ãƒŽãƒ¼ãƒˆ" @@ -2448,7 +2474,7 @@ _notification: youGotMention: "{name}ã‹ã‚‰ã®ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³" youGotReply: "{name}ã‹ã‚‰ã®ãƒªãƒ—ライ" youGotQuote: "{name}ã«ã‚ˆã‚‹å¼•用" - youRenoted: "{name}ãŒRenoteã—ã¾ã—ãŸ" + youRenoted: "{name}ãŒãƒªãƒŽãƒ¼ãƒˆã—ã¾ã—ãŸ" youWereFollowed: "フォãƒãƒ¼ã•れã¾ã—ãŸ" youReceivedFollowRequest: "フォãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒæ¥ã¾ã—ãŸ" yourFollowRequestAccepted: "フォãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆãŒæ‰¿èªã•れã¾ã—ãŸ" @@ -2476,7 +2502,7 @@ _notification: follow: "フォãƒãƒ¼" mention: "メンション" reply: "リプライ" - renote: "Renote" + renote: "リノート" quote: "引用" reaction: "リアクション" pollEnded: "アンケートãŒçµ‚了" @@ -2492,7 +2518,7 @@ _notification: _actions: followBack: "フォãƒãƒ¼ãƒãƒƒã‚¯" reply: "返信" - renote: "Renote" + renote: "リノート" _deck: alwaysShowMainColumn: "常ã«ãƒ¡ã‚¤ãƒ³ã‚«ãƒ©ãƒ を表示" @@ -2789,3 +2815,14 @@ _embedCodeGen: generateCode: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ã‚’作æˆ" codeGenerated: "コードãŒç”Ÿæˆã•れã¾ã—ãŸ" codeGeneratedDescription: "生æˆã•れãŸã‚³ãƒ¼ãƒ‰ã‚’ウェブサイトã«è²¼ã‚Šä»˜ã‘ã¦ã”利用ãã ã•ã„。" + +_selfXssPrevention: + warning: "è¦å‘Š" + title: "「ã“ã®ç”»é¢ã«ä½•ã‹è²¼ã‚Šä»˜ã‘ã‚ã€ã¯ã™ã¹ã¦è©æ¬ºã§ã™ã€‚" + description1: "ã“ã“ã«ä½•ã‹ã‚’貼り付ã‘ã‚‹ã¨ã€æ‚ªæ„ã®ã‚るユーザーã«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚’ä¹—ã£å–られãŸã‚Šã€å€‹äººæƒ…å ±ã‚’ç›—ã¾ã‚ŒãŸã‚Šã™ã‚‹å¯èƒ½æ€§ãŒã‚りã¾ã™ã€‚" + description2: "貼り付ã‘よã†ã¨ã—ã¦ã„ã‚‹ã‚‚ã®ãŒä½•ãªã®ã‹ã‚’æ£ç¢ºã«ç†è§£ã—ã¦ã„ãªã„å ´åˆã¯ã€%c今ã™ã作æ¥ã‚’䏿¢ã—ã¦ã“ã®ã‚¦ã‚£ãƒ³ãƒ‰ã‚¦ã‚’é–‰ã˜ã¦ãã ã•ã„。" + description3: "詳ã—ãã¯ã“ã¡ã‚‰ã‚’ã”確èªãã ã•ã„。 {link}" + +_followRequest: + recieved: "å—ã‘å–ã£ãŸç”³è«‹" + sent: "é€ã£ãŸç”³è«‹" diff --git a/locales/ja-KS.yml b/locales/ja-KS.yml index 0a8b3828f2..c3e0096926 100644 --- a/locales/ja-KS.yml +++ b/locales/ja-KS.yml @@ -8,6 +8,9 @@ search: "探ã™" notifications: "通知" username: "ユーザーå" password: "パスワード" +initialPasswordForSetup: "åˆæœŸè¨å®šé–‹å§‹ç”¨ãƒ‘スワード" +initialPasswordIsIncorrect: "åˆæœŸè¨å®šé–‹å§‹ç”¨ã®ãƒ‘スワードãŒã¡ã‚ƒã†ã§ã€‚" +initialPasswordForSetupDescription: "Miskkeyを自分ã§ã‚¤ãƒ³ã‚¹ãƒˆãƒ¼ãƒ«ã—ãŸã‚“ã‚„ã£ãŸã‚‰ã€è¨å®šãƒ•ァイルã«å…¥ã‚ŒãŸãƒ‘スワードを使ã£ã¦ã‚„。\nホスティングサービスを使ã£ã¨ã‚‹ã‚“ã‚„ã£ãŸã‚‰ã€ã‚µãƒ¼ãƒ“スã‹ã‚‰è¨€ã‚れãŸã‚„ã¤ã‚’使ã†ã‚“ã‚„ã§ã€‚\n別ã«ä½•ã‚‚è¨å®šã—ã¨ã‚‰ã‚“ã®ã‚„ã£ãŸã‚‰ã€ä½•も入れãšã«ç©ºã‘ã¨ã„ã¦ãªã€‚" forgotPassword: "パスワード忘れãŸã‚“?" fetchingAsApObject: "今ã¡ã¨é€£åˆã«ç…§ä¼šã—ã¨ã‚‹ã§" ok: "ãˆãˆã§" @@ -236,6 +239,8 @@ silencedInstances: "サーãƒãƒ¼ã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã•れã¦ã‚“ãã‚“" silencedInstancesDescription: "サイレンスã—ãŸã„サーãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã™ã‚“ã§ã€‚サイレンスã•れãŸã‚µãƒ¼ãƒãƒ¼ã«æ‰€å±žã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¯ã™ã¹ã¦ã€Œã‚µã‚¤ãƒ¬ãƒ³ã‚¹ã€ã¨ã—ã¦æ‰±ã‚れã€ãƒ•ã‚©ãƒãƒ¼ãŒã™ã¹ã¦ãƒªã‚¯ã‚¨ã‚¹ãƒˆã«ãªã‚Šã€ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã§ãªã„ãƒãƒ¼ã‚«ãƒ«ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã¯ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ã§ããªããªã‚“ãん。ブãƒãƒƒã‚¯ã—ãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã«ã¯å½±éŸ¿ã›ãƒ¼ã¸ã‚“ã§ã€‚" mediaSilencedInstances: "メディアサイレンスã—ãŸã‚µãƒ¼ãƒãƒ¼" mediaSilencedInstancesDescription: "メディアサイレンスã—ãŸã„サーãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’改行ã§åŒºåˆ‡ã£ã¦è¨å®šã™ã‚‹ã§ã€‚メディアサイレンスã•れãŸã‚µãƒ¼ãƒãƒ¼ã«æ‰€å±žã™ã‚‹ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«ã‚ˆã‚‹ãƒ•ァイルã¯ã™ã¹ã¦ã‚»ãƒ³ã‚·ãƒ†ã‚£ãƒ–ã¨ã—ã¦æ‰±ã‚れã¦ãªã€ã‚«ã‚¹ã‚¿ãƒ 絵文å—ãŒä½¿ãˆã¸ã‚“よã†ã«ãªã‚‹ã§ã€‚ブãƒãƒƒã‚¯ã—ãŸã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ã«ã¯å½±éŸ¿ã›ãˆã¸ã‚“ã§ã€‚" +federationAllowedHosts: "連åˆã‚’許ã™ã‚µãƒ¼ãƒãƒ¼" +federationAllowedHostsDescription: "連åˆã—ã¦ã‚‚ã„ã„サーãƒãƒ¼ã®ãƒ›ã‚¹ãƒˆã‚’行ã”ã¨ã«åŒºåˆ‡ã£ã¦è¨å®šã—ã¦ã‚„。" muteAndBlock: "ミュートã¨ãƒ–ãƒãƒƒã‚¯" mutedUsers: "ミュートã—ã¨ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼" blockedUsers: "ブãƒãƒƒã‚¯ã—ã¨ã‚‹ãƒ¦ãƒ¼ã‚¶ãƒ¼" @@ -334,6 +339,7 @@ renameFolder: "フォルダーåを変ãˆã‚‹" deleteFolder: "フォルダーをã»ã‹ã™" folder: "フォルダー" addFile: "ãƒ•ã‚¡ã‚¤ãƒ«ã‚’è¿½åŠ " +showFile: "ファイル出ã™" emptyDrive: "ドライブã¯ç©ºã£ã½ã‚„" emptyFolder: "ã“ã®ãƒ•ォルダーã¯ç©ºã‚„" unableToDelete: "消ã›ã‚“ã‹ã£ãŸã‚" @@ -376,7 +382,6 @@ enableLocalTimeline: "ãƒãƒ¼ã‚«ãƒ«ã‚¿ã‚¤ãƒ ラインを使ãˆã‚‹ã‚ˆã†ã«ã™ã‚‹ã enableGlobalTimeline: "ã‚°ãƒãƒ¼ãƒãƒ«ã‚¿ã‚¤ãƒ ラインを使ãˆã‚‹ã‚ˆã†ã«ã™ã‚‹ã‚" disablingTimelinesInfo: "ã“ã“らã¸ã‚“ã®ã‚¿ã‚¤ãƒ ラインを使ãˆã‚“よã†ã«ã—ã¦ã—ã‚‚ã¦ã‚‚ã€ç®¡ç†è€…ã¨ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ã¯ä½¿ãˆã‚‹ã¾ã¾ã«ãªã£ã¦ã‚‹ã§ã€ãã†ã‚„ãªã‹ã£ãŸã‚‰ä¸ä¾¿ã‚„ã‹ã‚‰ãªã€‚" registration: "登録" -enableRegistration: "一見ã•ã‚“ã§ã‚‚誰ã§ã‚‚ã„らã£ã—ゃ~ã„" invite: "æ¥ã¦ã‚„" driveCapacityPerLocalAccount: "ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼ã¯ã‚“ã²ã¨ã‚Šã‚ãŸã‚Šã®ãƒ‰ãƒ©ã‚¤ãƒ–容é‡" driveCapacityPerRemoteAccount: "リモートユーザーã¯ã‚“ã²ã¨ã‚Šã‚ãŸã‚Šã®ãƒ‰ãƒ©ã‚¤ãƒ–容é‡" @@ -448,6 +453,7 @@ totpDescription: "èªè¨¼ã‚¢ãƒ—リ使ã†ã¦ãƒ¯ãƒ³ã‚¿ã‚¤ãƒ パスワードを入゠moderator: "モデレーター" moderation: "モデレーション" moderationNote: "モデレーションノート" +moderationNoteDescription: "モデレーターã®ä¸ã ã‘ã§å…±æœ‰ã™ã‚‹ãƒ¡ãƒ¢ã‚’入れれるã§ã€‚" addModerationNote: "ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãƒŽãƒ¼ãƒˆã‚’è¿½åŠ ã™ã‚‹ã§" moderationLogs: "モデãƒã‚°" nUsersMentioned: "{n}äººãŒæŠ•ç¨¿" @@ -509,6 +515,10 @@ uiLanguage: "UIã®è¡¨ç¤ºè¨€èªž" aboutX: "{x}ã«ã¤ã„ã¦" emojiStyle: "絵文å—ã®ã‚¹ã‚¿ã‚¤ãƒ«" native: "ãƒã‚¤ãƒ†ã‚£ãƒ–" +menuStyle: "メニューã®ã‚¹ã‚¿ã‚¤ãƒ«" +style: "スタイル" +drawer: "ドãƒãƒ¯ãƒ¼" +popup: "ãƒãƒƒãƒ—アップ" showNoteActionsOnlyHover: "ãƒŽãƒ¼ãƒˆã®æ“作部をホãƒãƒ¼æ™‚ã®ã¿è¡¨ç¤ºã™ã‚‹ã§" showReactionsCount: "ノートã®ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³æ•°ã‚’表示ã™ã‚‹" noHistory: "å±¥æ´ã¯ãªã„ã‚。" @@ -591,6 +601,8 @@ ascendingOrder: "å°ã•ã„é †" descendingOrder: "大ãã„é †" scratchpad: "スクラッãƒãƒ‘ッド" scratchpadDescription: "スクラッãƒãƒ‘ッドã§ã¯AiScriptを色々試ã™ã“ã¨ãŒã§ãるんや。Misskeyã«å¯¾ã—ã¦è‰²ã€…ã§ãるコードを書ã„ã¦å‹•ã‹ã—ã¦ã¿ãŸã‚Šã€çµæžœã‚’見ãŸã‚Šã§ãã‚‹ã§ã€‚" +uiInspector: "UIインスペクター" +uiInspectorDescription: "メモリ上ã«ã‚ã‚‹UIコンãƒãƒ¼ãƒãƒ³ãƒˆã®ã‚¤ãƒ³ã‚¹ã‚¿ãƒ³ã‚¹ä¸€è¦§ã‚’見れるã§ã€‚UIコンãƒãƒ¼ãƒãƒ³ãƒˆã¯Ui:C:系関数ã§ç”Ÿæˆã•れるã§ã€‚" output: "出力" script: "スクリプト" disablePagesScript: "Pagesã®ã‚¹ã‚¯ãƒªãƒ—トを無効ã«ã—ã¦ã‚„" @@ -909,6 +921,7 @@ followersVisibility: "フォãƒãƒ¯ãƒ¼ã®å…¬é–‹ç¯„囲" continueThread: "ã•らã«ã‚¹ãƒ¬ãƒƒãƒ‰ã‚’見るã§" deleteAccountConfirm: "アカウントを消ã™ã§ï¼Ÿãˆãˆã‚“ã‹ï¼Ÿ" incorrectPassword: "パスワードãŒã¡ã‚ƒã†ã‚。" +incorrectTotp: "ワンタイムパスワードãŒé–“é•ã£ã¨ã‚‹ã‹ã€æœŸé™ãŒåˆ‡ã‚Œã¨ã‚‹ã¿ãŸã„ã‚„ãªã€‚" voteConfirm: "「{choice}ã€ã«æŠ•票ã™ã‚‹ã‚“ã‹ï¼Ÿ" hide: "éš ã™" useDrawerReactionPickerForMobile: "ケータイã¨ã‹ã®ã¨ãドãƒãƒ¯ãƒ¼ã§è¡¨ç¤ºã™ã‚‹ã§" @@ -1073,6 +1086,7 @@ retryAllQueuesConfirmTitle: "ã‚‚ã£ã‹ã„ã‚„ã£ã¦ã¿ã‚‹ã‹ï¼Ÿ" retryAllQueuesConfirmText: "一時的ã«ã‚µãƒ¼ãƒãƒ¼é‡ãªã‚‹ã‹ã‚‚ã—れã¸ã‚“ã§ã€‚" enableChartsForRemoteUser: "リモートユーザーã®ãƒãƒ£ãƒ¼ãƒˆã‚’作る" enableChartsForFederatedInstances: "リモートサーãƒãƒ¼ã®ãƒãƒ£ãƒ¼ãƒˆã‚’作る" +enableStatsForFederatedInstances: "リモートサーãƒã®æƒ…å ±ã‚’å–å¾—" showClipButtonInNoteFooter: "ノートã®ã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã«ã‚¯ãƒªãƒƒãƒ—ã‚’è¿½åŠ " reactionsDisplaySize: "ツッコミã®è¡¨ç¤ºã®ã§ã‹ã•" limitWidthOfReaction: "ãƒ„ãƒƒã‚³ãƒŸã®æœ€å¤§æ¨ªå¹…を制é™ã—ã¦ã€ã¡ã£ã•ã表示ã™ã‚‹ã§" @@ -1259,6 +1273,32 @@ confirmWhenRevealingSensitiveMedia: "センシティブãªãƒ¡ãƒ‡ã‚£ã‚¢ã‚’表示ã sensitiveMediaRevealConfirm: "センシティブãªãƒ¡ãƒ‡ã‚£ã‚¢ã‚„ã§ã€‚表示ã™ã‚‹ã‚“ã‹ï¼Ÿ" createdLists: "作æˆã—ãŸãƒªã‚¹ãƒˆ" createdAntennas: "作æˆã—ãŸã‚¢ãƒ³ãƒ†ãƒŠ" +fromX: "{x}ã‹ã‚‰" +genEmbedCode: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ã‚’作る" +noteOfThisUser: "ã“ã®ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®ãƒŽãƒ¼ãƒˆå…¨éƒ¨" +clipNoteLimitExceeded: "ã“れ以上ã“ã®ã‚¯ãƒªãƒƒãƒ—ã«ãƒŽãƒ¼ãƒˆè¿½åŠ ã§ã‘ã¸ã‚“ã‚。" +performance: "パフォーマンス" +modified: "変更ã‚り" +discard: "ã‚„ã‚ã‚‹" +thereAreNChanges: "{n}個ã®å¤‰æ›´ãŒã‚ã‚‹ã¿ãŸã„ã‚„" +signinWithPasskey: "パスã‚ーã§ãƒã‚°ã‚¤ãƒ³" +unknownWebAuthnKey: "登録ã•れã¦ã¸ã‚“パスã‚ーやãªã€‚" +passkeyVerificationFailed: "パスã‚ãƒ¼ã®æ¤œè¨¼ã«å¤±æ•—ã—ãŸã§ã€‚" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "パスã‚ãƒ¼ã®æ¤œè¨¼ã¯æˆåŠŸã—ãŸã‚“ã‚„ã‘ã©ã€ãƒ‘スワードレスãƒã‚°ã‚¤ãƒ³ãŒç„¡åйã«ãªã£ã¨ã‚‹ã‚。" +messageToFollower: "フォãƒãƒ¯ãƒ¼ã¸ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸" +target: "対象" +testCaptchaWarning: "CAPTCHAã®ãƒ†ã‚¹ãƒˆã‚’目的ã¨ã—ã¦ã‚‹ã§ã€‚<strong>çµ¶å¯¾ã«æœ¬ç•ªç’°å¢ƒã§ä½¿ã‚ã‚“ã¨ã„ã¦ãªã€‚絶対やã§ã€‚</strong>" +prohibitedWordsForNameOfUser: "ç¦æ¢ãƒ¯ãƒ¼ãƒ‰ï¼ˆãƒ¦ãƒ¼ã‚¶ãƒ¼å)" +prohibitedWordsForNameOfUserDescription: "ã“ã®ãƒªã‚¹ãƒˆã®ä¸ã«ã‚ã‚‹æ–‡å—列ãŒãƒ¦ãƒ¼ã‚¶ãƒ¼åã«å…¥ã£ã¨ã£ãŸã‚‰ã€ãã®åå‰ã«å¤‰æ›´ã§ãã²ã‚“よã†ã«ãªã‚‹ã§ã€‚モデレーター権é™ãŒã‚るユーザーã¯é™¤å¤–や。" +yourNameContainsProhibitedWords: "ãã®åå‰ã¯ç¦æ¢ã—ãŸæ–‡å—列ãŒå«ã¾ã‚Œã¨ã‚‹ã§" +yourNameContainsProhibitedWordsDescription: "ãã®åå‰ã¯ç¦æ¢ã—ãŸæ–‡å—列ãŒå«ã¾ã‚Œã¨ã‚‹ã‚。ã©ã†ã—ã¦ã‚‚ã£ã¦è¨€ã†ãªã‚‰ã€ã‚µãƒ¼ãƒãƒ¼ç®¡ç†è€…ã«è¨€ã†ã—ã‹ãªã„ã§ã€‚" +_abuseUserReport: + forward: "転é€" + forwardDescription: "匿åã®ã‚·ã‚¹ãƒ†ãƒ アカウントã£ã¦ã“ã¨ã«ã—ã¦ã€ãƒªãƒ¢ãƒ¼ãƒˆã‚µãƒ¼ãƒãƒ¼ã«é€šå ±ã‚’転é€ã™ã‚‹ã§ã€‚" + resolve: "解決" + accept: "ãˆãˆã‚ˆ" + reject: "ã‚ã‹ã‚“よ" + resolveTutorial: "内容ãŒãˆãˆãªã‚‰ã€Œãˆãˆã‚ˆã€ã‚’é¸ã¶ã‚“や。肯定的ã«è§£æ±ºã•れãŸã“ã¨ã«ã—ã¦è¨˜éŒ²ã™ã‚‹ã§ã€‚\n逆ã«ã€å†…容ãŒã ã‚ãªã‚‰ã€Œã‚ã‹ã‚“よã€ã‚’é¸ã³ã„や。å¦å®šçš„ã«è§£æ±ºã•れãŸã£ã¦è¨˜éŒ²ã—ã¨ãã§ã€‚" _delivery: status: "é…信状態" stop: "é…ä¿¡ã›ã‡ã¸ã‚“" @@ -1393,8 +1433,10 @@ _serverSettings: fanoutTimelineDescription: "入れるã¨ã€ãŠã®ãŠã®ã‚¿ã‚¤ãƒ ラインをå–å¾—ã™ã‚‹ã¨ãã«ã‚ã¡ã‚ƒã‚ã¡ã‚ƒå‹•ããŒè‰¯ã†ãªã£ã¦ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ãŒè»½ããªã‚‹ã‚。ã§ã‚‚ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ã†é‡ãŒå¢—ãˆã‚‹ã‹ã‚‰æ³¨æ„ãªã€‚サーãƒãƒ¼ã®ãƒ¡ãƒ¢ãƒªãŒè¶³ã‚Šã‚“ã¨ãã¨ã‹ã€å‹•ããŒå¤‰ãªã¨ãã¯åˆ‡ã‚Œã‚‹ã§ã€‚" fanoutTimelineDbFallback: "データベースã«ãƒ•ォールãƒãƒƒã‚¯ã™ã‚‹" fanoutTimelineDbFallbackDescription: "有効ã«ã—ãŸã‚‰ã€ã‚¿ã‚¤ãƒ ラインãŒã‚ャッシュんä¸ã«å…¥ã£ã¦ãªã„ã¨ãã«DBã«ã‚‚ã£ã‹ã„å•ã„åˆã‚ã›ã‚‹ãƒ•ォールãƒãƒƒã‚¯å‡¦ç†ã£ã¦ã®ã‚’ã‚„ã£ã¨ãã§ã€‚切ã£ãŸã‚‰ãƒ•ォールãƒãƒƒã‚¯å‡¦ç†ã‚’やらんã‹ã‚‰ã‚µãƒ¼ãƒãƒ¼ã¯ã‚‚ã£ã¨è»½ããªã‚“ãã‚“ã‘ã©ã€ã‚¿ã‚¤ãƒ ラインã®å–得範囲ãŒã¡ã‚‡ã£ã¨æ¸›ã‚‹ã§ã€‚" + reactionsBufferingDescription: "有効ã«ã—ãŸã‚‰ã€ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ä½œã‚‹ã¨ãã®ãƒ‘フォーマンスãŒã™ã£ã”ã„上ãŒã£ã¦ã€ãƒ‡ãƒ¼ã‚¿ãƒ™ãƒ¼ã‚¹ã¸ã®è² è·ãŒæ¸›ã‚‹ã§ã€‚代ã‚りã«ã€Redisã®ãƒ¡ãƒ¢ãƒªä½¿ç”¨ã¯å¢—ãˆã‚‹ã§ã€‚" inquiryUrl: "å•ã„åˆã‚ã›å…ˆURL" inquiryUrlDescription: "サーãƒãƒ¼é‹å–¶è€…ã¸ã®ãŠå•ã„åˆã‚ã›ãƒ•ォームã®URLã‚„ã€é‹å–¶è€…ã®é€£çµ¡å…ˆç‰ãŒè¨˜è¼‰ã•れãŸWebページã®URLを指定ã™ã‚‹ã§ã€‚" + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "一定期間モデレーターãŒãŠã‚‰ã‚“ã‹ã£ãŸã‚‰ã€ã‚¹ãƒ‘ムを防ããŸã‚ã«ã“ã®è¨å®šã¯å‹æ‰‹ã«åˆ‡ã‚‰ã‚Œã‚‹ã§ã€‚" _accountMigration: moveFrom: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‹ã‚‰ã“ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã«å¼•ã£è¶Šã™" moveFromSub: "別ã®ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã¸ã‚¨ã‚¤ãƒªã‚¢ã‚¹ã‚’作る" @@ -1726,6 +1768,11 @@ _role: canSearchNotes: "ノート探ã›ã‚‹ã‹ã©ã†ã‹" canUseTranslator: "翻訳使ãˆã‚‹ã‹ã©ã†ã‹" avatarDecorationLimit: "アイコンデコã®ã„ã£ã¡ã°ã‚“ã¤ã‘れる数" + canImportAntennas: "アンテナã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許ã™" + canImportBlocking: "ブãƒãƒƒã‚¯ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許ã™" + canImportFollowing: "フォãƒãƒ¼ã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許ã™" + canImportMuting: "ミュートã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許ã™" + canImportUserLists: "リストã®ã‚¤ãƒ³ãƒãƒ¼ãƒˆã‚’許ã™" _condition: roleAssignedTo: "マニュアルãƒãƒ¼ãƒ«ã«ã‚¢ã‚µã‚¤ãƒ³æ¸ˆã¿" isLocal: "ãƒãƒ¼ã‚«ãƒ«ãƒ¦ãƒ¼ã‚¶ãƒ¼" @@ -2219,6 +2266,9 @@ _profile: changeBanner: "ãƒãƒŠãƒ¼ç”»åƒã‚’変更ã™ã‚‹ã§" verifiedLinkDescription: "内容をURLã«è¨å®šã™ã‚‹ã¨ã€ãƒªãƒ³ã‚¯å…ˆã®webサイトã«è‡ªåˆ†ã®ãƒ—ãƒãƒ•ã®ãƒªãƒ³ã‚¯ãŒå«ã¾ã‚Œã¦ã‚‹å ´åˆã«æ‰€æœ‰è€…ç¢ºèªæ¸ˆã¿ã‚¢ã‚¤ã‚³ãƒ³ã‚’表示ã•ã›ã‚‹ã“ã¨ãŒã§ãã‚‹ã§ã€‚" avatarDecorationMax: "最大{max}ã¤ã¾ã§ãƒ‡ã‚³ã¤ã‘れんã§" + followedMessage: "フォãƒãƒ¼ã•れãŸã‚‰è¿”ã™ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸" + followedMessageDescription: "フォãƒãƒ¼ã•れãŸã¨ãã«ç›¸æ‰‹ã«è¿”ã™çŸã‚ã®ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ã‚’決ã‚れるã§ã€‚" + followedMessageDescriptionForLockedAccount: "フォãƒãƒ¼ãŒæ‰¿èªåˆ¶ãªã‚‰ã€ãƒ•ã‚©ãƒãƒ¼ãƒªã‚¯ã‚¨ã‚¹ãƒˆã‚’OKã—ãŸã¨ãã«è¦‹ã›ã‚‹ã§ã€‚" _exportOrImport: allNotes: "å…¨ã¦ã®ãƒŽãƒ¼ãƒˆ" favoritedNotes: "ãŠæ°—ã«å…¥ã‚Šã«ã—ãŸãƒŽãƒ¼ãƒˆ" @@ -2311,6 +2361,7 @@ _pages: eyeCatchingImageSet: "アイã‚ャッãƒç”»åƒã‚’è¨å®š" eyeCatchingImageRemove: "アイã‚ャッãƒç”»åƒã‚’削除" chooseBlock: "ブãƒãƒƒã‚¯ã‚’è¿½åŠ " + enterSectionTitle: "セクションタイトルを入れる" selectType: "ç¨®é¡žã‚’é¸æŠž" contentBlocks: "コンテンツ" inputBlocks: "入力" @@ -2356,13 +2407,15 @@ _notification: renotedBySomeUsers: "{n}人ãŒãƒªãƒŽãƒ¼ãƒˆã—ãŸã§" followedBySomeUsers: "{n}人ã«ãƒ•ã‚©ãƒãƒ¼ã•れãŸã§" flushNotification: "通知ã®å±¥æ´ã‚’リセットã™ã‚‹" + exportOfXCompleted: "{x}ã®ã‚¨ã‚¯ã‚¹ãƒãƒ¼ãƒˆãŒçµ‚ã‚ã£ãŸã‚" + login: "ãƒã‚°ã‚¤ãƒ³ã—ã¨ã£ãŸã§" _types: all: "ã™ã¹ã¦" note: "ã‚ã‚“ãŸã‚‰ã®æ–°è¦æŠ•稿" follow: "フォãƒãƒ¼" mention: "メンション" reply: "リプライ" - renote: "Renote" + renote: "リノート" quote: "引用" reaction: "ツッコミ" pollEnded: "アンケートãŒçµ‚了ã—ãŸã§" @@ -2370,12 +2423,14 @@ _notification: followRequestAccepted: "フォãƒãƒ¼ãŒå—ç†ã•れãŸã§" roleAssigned: "ãƒãƒ¼ãƒ«ãŒä»˜ä¸Žã•れãŸ" achievementEarned: "実績ã®ç²å¾—" + exportCompleted: "エクスãƒãƒ¼ãƒˆçµ‚ã‚ã£ãŸ" login: "ãƒã‚°ã‚¤ãƒ³" + test: "通知テスト" app: "連æºã‚¢ãƒ—リã‹ã‚‰ã®é€šçŸ¥ã‚„" _actions: followBack: "フォãƒãƒ¼ãƒãƒƒã‚¯" reply: "返事" - renote: "Renote" + renote: "リノート" _deck: alwaysShowMainColumn: "ã„ã¤ã‚‚メインカラムを表示" columnAlign: "カラムã®å¯„ã›" @@ -2436,7 +2491,10 @@ _webhookSettings: abuseReport: "ユーザーã‹ã‚‰é€šå ±ãŒã‚ã£ãŸã¨ã" abuseReportResolved: "ユーザーã‹ã‚‰ã®é€šå ±ã‚’処ç†ã—ãŸã¨ã" userCreated: "ユーザーãŒä½œæˆã•れãŸã¨ã" + inactiveModeratorsWarning: "モデレーターãŒã—ã°ã‚‰ããŠã‚‰ã‚“ã‹ã£ãŸã¨ã" + inactiveModeratorsInvitationOnlyChanged: "モデレーターãŒã—ã°ã‚‰ããŠã‚‰ã‚“ã‹ã£ãŸã‹ã‚‰ã€ã‚·ã‚¹ãƒ†ãƒ ãŒæ‹›å¾…制ã«å¤‰ãˆãŸã¨ã" deleteConfirm: "ã»ã‚“ã¾ã«Webhookã‚’ã»ã‹ã—ã¦ã‚‚ãˆãˆã‚“ã‹ï¼Ÿ" + testRemarks: "スイッãƒå³ã®ãƒœã‚¿ãƒ³ã‚’押ã™ã¨ãƒ€ãƒŸãƒ¼ãƒ‡ãƒ¼ã‚¿ã‚’使ã£ãŸãƒ†ã‚¹ãƒˆç”¨Webhookã‚’é€ã‚Œã‚‹ã§ã€‚" _abuseReport: _notificationRecipient: createRecipient: "é€šå ±ã®é€šçŸ¥å…ˆã‚’è¿½åŠ " @@ -2480,6 +2538,8 @@ _moderationLogTypes: markSensitiveDriveFile: "ファイルをセンシティブ付与" unmarkSensitiveDriveFile: "ファイルをセンシティブ解除" resolveAbuseReport: "苦情を解決" + forwardAbuseReport: "é€šå ±ã‚’è»¢é€" + updateAbuseReportNote: "é€šå ±ã®ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚·ãƒ§ãƒ³ãƒŽãƒ¼ãƒˆæ›´æ–°" createInvitation: "招待コード作る" createAd: "広告を作んã§" deleteAd: "広告ã»ã‹ã™" @@ -2491,6 +2551,14 @@ _moderationLogTypes: unsetUserBanner: "ã“ã®åã®ãƒãƒŠãƒ¼å…ƒã«æˆ»ã™" createSystemWebhook: "SystemWebhookを作æˆ" updateSystemWebhook: "SystemWebhookã‚’æ›´æ–°" + deleteSystemWebhook: "SystemWebhookを削除" + createAbuseReportNotificationRecipient: "é€šå ±ã®é€šçŸ¥å…ˆä½œã‚‹" + updateAbuseReportNotificationRecipient: "é€šå ±ã®é€šçŸ¥å…ˆæ›´æ–°" + deleteAbuseReportNotificationRecipient: "é€šå ±ã®é€šçŸ¥å…ˆæ¶ˆã™" + deleteAccount: "アカウント消ã™" + deletePage: "ページ消ã™" + deleteFlash: "Playã‚’ã»ã‹ã™" + deleteGalleryPost: "ã‚®ãƒ£ãƒ©ãƒªãƒ¼ã®æŠ•ç¨¿ã‚’ã»ã‹ã™" _fileViewer: title: "ファイルã®è©³ã—ã„æƒ…å ±" type: "ファイルã®ç¨®é¡ž" @@ -2622,3 +2690,22 @@ _mediaControls: pip: "ピクãƒãƒ£ã‚¤ãƒ³ãƒ”クãƒãƒ£" playbackRate: "å†ç”Ÿé€Ÿåº¦" loop: "ループå†ç”Ÿ" +_contextMenu: + title: "コンテã‚ストメニュー" + app: "アプリ" + appWithShift: "Shiftã‚ーã§ã‚¢ãƒ—リ" + native: "ブラウザã®UI" +_embedCodeGen: + title: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ã‚’カスタム" + header: "ヘッダー出ã™" + autoload: "勿‰‹ã«ç¶šãã‚’èªã¿è¾¼ã‚€ï¼ˆéžæŽ¨å¥¨ï¼‰" + maxHeight: "高ã•ã®æœ€å¤§å€¤" + maxHeightDescription: "0ã¯æœ€å¤§å€¤ã‚’指定ã›ãˆã¸ã‚“ã‘ã©ã€ã‚¦ã‚£ã‚¸ã‚§ãƒƒãƒˆãŒä¼¸ã³ç¶šã‘ã‚‹ã‹ã‚‰çµ¶å¯¾1以上ã«ã—ã¨ã„ã¦ã‚„。" + maxHeightWarn: "高ã•ã®æœ€å¤§å€¤ãŒç„¡åйã«ãªã£ã¨ã‚‹ã§ã€‚æ„図ã—ã¦ã¸ã‚“変更ãªã‚‰ã€æ™®é€šã®å€¤ã«æˆ»ã—ã¦ã‚„。" + previewIsNotActual: "プレビュー画é¢ã§å‡ºã›ã‚‹ç¯„囲をã¯ã¿å‡ºã—ãŸã‹ã‚‰ã€ãƒ›ãƒ³ãƒžã®è¡¨ç¤ºã¨ã¯ã¡ã‚ƒã†ã¨ãŠã‚‚ã†ã§ã€‚" + rounded: "角丸ã‚ã‚‹" + border: "å¤–æž ã«æž ç·šã¤ã‘ã‚‹" + applyToPreview: "プレビューã«åæ˜ " + generateCode: "埋ã‚è¾¼ã¿ã‚³ãƒ¼ãƒ‰ä½œã‚‹" + codeGenerated: "コード作ã£ãŸã§" + codeGeneratedDescription: "作ã£ãŸã‚³ãƒ¼ãƒ‰ã¯ã‚¦ã‚§ãƒ–サイトã«è²¼ã£ã¤ã‘ã¦ä½¿ã£ã¦ã‚„。" diff --git a/locales/ko-GS.yml b/locales/ko-GS.yml index 6c667b48da..60b82d5db9 100644 --- a/locales/ko-GS.yml +++ b/locales/ko-GS.yml @@ -356,7 +356,6 @@ enableLocalTimeline: "로컬 타임ë¼ì¸ 키기" enableGlobalTimeline: "글로벌 타임ë¼ì¸ 키기" disablingTimelinesInfo: "ìš” 타임ë¼ì¸ì–¼ êº¼ë„ ê°„ë¦¬ìží•˜ê³ 중재ìžë„Œ ê³ ëŒ€ë¡œ 설 수 잇ì‹ë‹ˆë‹¤." registration: "맨걸기" -enableRegistration: "누ë¼ë„ 새로 맨걸 수 잇거로 하기" invite: "초대하기" driveCapacityPerLocalAccount: "로컬 ì‚¬ìš©ìž í•˜ë‚˜ë§ˆì¤‘ 드ë¼ì´ë¸Œ 커기" driveCapacityPerRemoteAccount: "ì›¬ê² ì‚¬ìš©ìž í•˜ë‚˜ë§ˆì¤‘ 드ë¼ì´ë¸Œ 커기" @@ -468,7 +467,7 @@ tooShort: "억수로 짜립니다" tooLong: "억수로 집니다" passwordMatched: "ë§žì‹ë‹ˆë‹¤" passwordNotMatched: "안 ë§žì‹ë‹ˆë‹¤" -signinWith: "{n}서 로그ì¸" +signinWith: "{x} 서 로그ì¸" signinFailed: "ë¡œê·¸ì¸ ëª¬ í–ˆì‹ë‹ˆë‹¤. ê³ ì´ë¦„ì´ëž‘ 비밀번호 ì œëŒ€ë¡œ ì¼ëŠ”ê°€ 확ì¸í•´ 주ì´ì†Œ." or: "아니면" language: "언어" @@ -809,11 +808,13 @@ _notification: _types: follow: "팔로잉" mention: "멘션" + renote: "리노트" quote: "따오기" reaction: "반엉" login: "로그ì¸" _actions: reply: "답하기" + renote: "리노트" _deck: _columns: notifications: "알림" diff --git a/locales/ko-KR.yml b/locales/ko-KR.yml index 414202adab..d694d2dbae 100644 --- a/locales/ko-KR.yml +++ b/locales/ko-KR.yml @@ -42,7 +42,7 @@ favorite: "ì¦ê²¨ì°¾ê¸°" favorites: "ì¦ê²¨ì°¾ê¸°" unfavorite: "ì¦ê²¨ì°¾ê¸°ì—서 ì œê±°" favorited: "ì¦ê²¨ì°¾ê¸°ì— 등ë¡í–ˆìŠµë‹ˆë‹¤." -alreadyFavorited: "ì´ë¯¸ ì¦ê²¨ì°¾ê¸°ì— 등ë¡í–ˆìŠµë‹ˆë‹¤." +alreadyFavorited: "ì´ë¯¸ ì¦ê²¨ì°¾ê¸°ì— 등ë¡ë˜ì–´ 있습니다." cantFavorite: "ì¦ê²¨ì°¾ê¸°ì— 등ë¡í•˜ì§€ 못했습니다." pin: "í”„ë¡œí•„ì— ê³ ì •" unpin: "프로필ì—서 ê³ ì • í•´ì œ" @@ -382,7 +382,6 @@ enableLocalTimeline: "로컬 타임ë¼ì¸ 활성화" enableGlobalTimeline: "글로벌 타임ë¼ì¸ 활성화" disablingTimelinesInfo: "íŠ¹ì • 타임ë¼ì¸ì„ 비활성화하ë”ë¼ë„ ê´€ë¦¬ìž ë° ëª¨ë”ë ˆì´í„°ëŠ” ê³„ì† ì‚¬ìš©í• ìˆ˜ 있습니다." registration: "등ë¡" -enableRegistration: "ì‹ ê·œ 회ì›ê°€ìž…ì„ í™œì„±í™”" invite: "초대" driveCapacityPerLocalAccount: "로컬 ìœ ì € 한 명당 드ë¼ì´ë¸Œ 용량" driveCapacityPerRemoteAccount: "ì›ê²© 사용ìžë³„ 드ë¼ì´ë¸Œ 용량" @@ -587,6 +586,7 @@ masterVolume: "마스터 볼륨" notUseSound: "ìŒì†Œê±° 하기" useSoundOnlyWhenActive: "Misskey를 활성화한 때ì—ë§Œ 소리를 ì¶œë ¥í•˜ê¸°" details: "ìžì„¸ížˆ" +renoteDetails: "리노트 ìƒì„¸ ë‚´ìš©" chooseEmoji: "ì´ëª¨ì§€ ì„ íƒ" unableToProcess: "ìž‘ì—…ì„ ì™„ë£Œí• ìˆ˜ 없습니다" recentUsed: "최근 사용" @@ -947,6 +947,9 @@ oneHour: "1시간" oneDay: "1ì¼" oneWeek: "ì¼ì£¼ì¼" oneMonth: "1개월" +threeMonths: "3개월" +oneYear: "1ë…„" +threeDays: "3ì¼" reflectMayTakeTime: "ë°˜ì˜ë˜ê¸°ê¹Œì§€ ì‹œê°„ì´ ê±¸ë¦´ 수 있습니다." failedToFetchAccountInformation: "ê³„ì • ì •ë³´ë¥¼ ê°€ì ¸ì˜¤ì§€ 못했습니다" rateLimitExceeded: "ìš”ì² ì œí•œ 횟수를 초과하였습니다" @@ -1254,7 +1257,7 @@ lastNDays: "최근 {n}ì¼" backToTitle: "타ì´í‹€ë¡œ 가기" hemisphere: "거주 ì§€ì—" withSensitive: "민ê°í•œ 파ì¼ì´ í¬í•¨ëœ 노트 보기" -userSaysSomethingSensitive: "{name} ê°™ì€ ë¯¼ê°í•œ 파ì¼ì´ í¬í•¨ëœ 글" +userSaysSomethingSensitive: "{name}ì˜ ë¯¼ê°í•œ 파ì¼ì´ í¬í•¨ëœ 게시물" enableHorizontalSwipe: "스와ì´í”„하여 íƒ ì „í™˜" loading: "불러오는 중" surrender: "그만ë‘기" @@ -1286,13 +1289,30 @@ signinWithPasskey: "패스키로 로그ì¸" unknownWebAuthnKey: "등ë¡ë˜ì§€ ì•Šì€ íŒ¨ìŠ¤í‚¤ìž…ë‹ˆë‹¤." passkeyVerificationFailed: "패스키 ê²€ì¦ì„ 실패했습니다." passkeyVerificationSucceededButPasswordlessLoginDisabled: "패스키를 ê²€ì¦í–ˆìœ¼ë‚˜, 비밀번호 ì—†ì´ ë¡œê·¸ì¸í•˜ê¸°ê°€ êº¼ì ¸ 있습니다." -messageToFollower: "íŒ”ë¡œì›Œì— ë³´ë‚¼ 메시지" +messageToFollower: "팔로워ì—게 보낼 메시지" target: "대ìƒ" testCaptchaWarning: "CAPTCHA를 테스트하기 위한 기능입니다. <strong>ì‹¤ì œ 환경ì—서는 사용하지 마세요.</strong>" prohibitedWordsForNameOfUser: "금지 단어 (ì‚¬ìš©ìž ì´ë¦„)" prohibitedWordsForNameOfUserDescription: "ì´ ëª©ë¡ì— í¬í•¨ë˜ëŠ” 키워드가 ì‚¬ìš©ìž ì´ë¦„ì— ìžˆëŠ” 경우, ì¼ë°˜ 사용ìžëŠ” ì´ë¦„ì„ ë°”ê¿€ 수 없습니다. 모ë”ë ˆì´í„° ê¶Œí•œì„ ê°€ì§„ 사용ìžëŠ” ì œí•œ 대ìƒì—서 ì œì™¸ë©ë‹ˆë‹¤." yourNameContainsProhibitedWords: "ë°”ê¾¸ë ¤ëŠ” ì´ë¦„ì— ê¸ˆì§€ëœ í‚¤ì›Œë“œê°€ í¬í•¨ë˜ì–´ 있습니다." yourNameContainsProhibitedWordsDescription: "ì´ë¦„ì— ê¸ˆì§€ëœ í‚¤ì›Œë“œê°€ 있습니다. ì´ë¦„ì„ ì‚¬ìš©í•´ì•¼ 하는 경우, 서버 관리ìžì— 문ì˜í•˜ì„¸ìš”." +thisContentsAreMarkedAsSigninRequiredByAuthor: "게시ìžì— ì˜í•´ 로그ì¸í•´ì•¼ ë³¼ 수 있ë„ë¡ ì„¤ì •ë˜ì–´ 있습니다." +lockdown: "ìž ê¸ˆ" +pleaseSelectAccount: "ê³„ì •ì„ ì„ íƒí•´ì£¼ì„¸ìš”." +availableRoles: "사용 가능한 ì—í• " +acknowledgeNotesAndEnable: "활성화 하기 ì „ì— ì£¼ì˜ ì‚¬í•ì„ í™•ì¸í–ˆìŠµë‹ˆë‹¤." +_accountSettings: + requireSigninToViewContents: "콘í…ì¸ ì—´ëžŒì„ ìœ„í•´ 로그ì¸ìœ¼ 필수로 ì„¤ì •í•˜ê¸°" + requireSigninToViewContentsDescription1: "ìžì‹ ì´ ìž‘ì„±í•œ ëª¨ë“ ë…¸íŠ¸ ë“±ì˜ ì½˜í…ì¸ ë¥¼ 보기 위해 로그ì¸ì„ 필수로 ì„¤ì •í•©ë‹ˆë‹¤. í¬ë¡¤ëŸ¬ê°€ ì •ë³´ 수집하는 ê²ƒì„ ë°©ì§€í•˜ëŠ” 효과를 ê¸°ëŒ€í• ìˆ˜ 있습니다." + requireSigninToViewContentsDescription2: "URL 미리보기(OGP), 웹페ì´ì§€ì— 삽입, 노트 ì¸ìš©ì„ ì§€ì›í•˜ì§€ 않는 서버ì—서 ë³¼ 수 없게 ë©ë‹ˆë‹¤." + requireSigninToViewContentsDescription3: "ì›ê²© ì„œë²„ì— ì—°í•©ëœ ì½˜í…ì¸ ì—는 ì´ëŸ¬í•œ ì œí•œì´ ì ìš©ë˜ì§€ ì•Šì„ ìˆ˜ 있습니다." + makeNotesFollowersOnlyBefore: "과거 노트는 팔로워만 ë³¼ 수 있ë„ë¡ ì„¤ì •í•˜ê¸°" + makeNotesFollowersOnlyBeforeDescription: "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있는 ë™ì•ˆ, ì„¤ì •ëœ ë‚ ì§œ ë° ì‹œê°„ë³´ë‹¤ 과거 ë˜ëŠ” ì„¤ì •ëœ ì‹œê°„ì´ ì§€ë‚œ 노트는 팔로워만 ë³¼ 수 있게 ë©ë‹ˆë‹¤.비활성화하면 ë…¸íŠ¸ì˜ ê³µê°œ ìƒíƒœë„ ì›ëž˜ëŒ€ë¡œ ëŒì•„갑니다." + makeNotesHiddenBefore: "과거 노트 비공개로 ì „í™˜í•˜ê¸°" + makeNotesHiddenBeforeDescription: "ì´ ê¸°ëŠ¥ì´ í™œì„±í™”ë˜ì–´ 있는 ë™ì•ˆ ì„¤ì •í•œ ë‚ ì§œ ë° ì‹œê°„ë³´ë‹¤ 과거 ë˜ëŠ” ì„¤ì •í•œ ì‹œê°„ì´ ì§€ë‚œ 노트는 본ì¸ë§Œ ë³¼ 수 있게(비공개로 ì „í™˜) ë©ë‹ˆë‹¤. 비활성화하면 ë…¸íŠ¸ì˜ ê³µê°œ ìƒíƒœë„ ì›ëž˜ëŒ€ë¡œ ëŒì•„갑니다." + mayNotEffectForFederatedNotes: "ì›ê²© ì„œë²„ì— ì—°í•©ëœ ë…¸íŠ¸ì—는 효과가 ì—†ì„ ìˆ˜ë„ ìžˆìŠµë‹ˆë‹¤." + notesHavePassedSpecifiedPeriod: "ì§€ì •í•œ ì‹œê°„ì´ ê²½ê³¼ëœ ë…¸íŠ¸" + notesOlderThanSpecifiedDateAndTime: "ì§€ì •ëœ ë‚ ì§œ ë° ì‹œê°„ ì´ì „ì˜ ë…¸íŠ¸" _abuseUserReport: forward: "ì „ë‹¬" forwardDescription: "ìµëª… 시스템 ê³„ì •ì„ ì‚¬ìš©í•˜ì—¬ 리모트 ì„œë²„ì— ì‹ ê³ ë‚´ìš©ì„ ì „ë‹¬í• ìˆ˜ 있습니다." @@ -1437,6 +1457,8 @@ _serverSettings: reactionsBufferingDescription: "활성화 한 경우, 리액션 작성 í¼í¬ë¨¼ìŠ¤ê°€ 대í í–¥ìƒë˜ì–´ DBì˜ ë¶€í•˜ë¥¼ ì¤„ì¼ ìˆ˜ 있으나, Redisì˜ ë©”ëª¨ë¦¬ ì‚¬ìš©ëŸ‰ì´ ë§Žì•„ì§‘ë‹ˆë‹¤." inquiryUrl: "문ì˜ì²˜ URL" inquiryUrlDescription: "서버 ìš´ì˜ìžì—게 보내는 ë¬¸ì˜ ì–‘ì‹ì˜ URLì´ë‚˜ ìš´ì˜ìžì˜ ì—°ë½ì²˜ ë“±ì´ ì 힌 웹 페ì´ì§€ì˜ URLì„ ì„¤ì •í•©ë‹ˆë‹¤." + openRegistration: "íšŒì› ê°€ìž…ì„ í™œì„±í™” 하기" + openRegistrationWarning: "íšŒì› ê°€ìž…ì„ ê°œë°©í•˜ëŠ” ê²ƒì€ ë¦¬ìŠ¤í¬ê°€ 따릅니다. 서버를 í•ìƒ ê°ì‹œí• 수 ìžˆê³ , ë¬¸ì œê°€ ë°œìƒí–ˆì„ 때 바로 대ì‘í• ìˆ˜ 있는 ìƒíƒœì—서만 활성화 하는 ê²ƒì„ ê¶Œìž¥í•©ë‹ˆë‹¤." thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ì¼ì • 기간ë™ì•ˆ 모ë”ë ˆì´í„°ì˜ 활ë™ì´ ê°ì§€ë˜ì§€ 않는 경우, 스팸 방지를 위해 ì´ ì„¤ì •ì€ ìžë™ìœ¼ë¡œ 꺼집니다." _accountMigration: moveFrom: "다른 ê³„ì •ì—서 ì´ ê³„ì •ìœ¼ë¡œ ì´ì‚¬" @@ -2157,8 +2179,11 @@ _auth: permissionAsk: "ì´ ì•±ì€ ë‹¤ìŒì˜ ê¶Œí•œì„ ìš”ì²í•©ë‹ˆë‹¤" pleaseGoBack: "앱으로 ëŒì•„가서 시ë„í•´ 주세요" callback: "앱으로 ëŒì•„갑니다" + accepted: "ì ‘ê·¼ ê¶Œí•œì´ ë¶€ì—¬ë˜ì—ˆìŠµë‹ˆë‹¤." denied: "ì ‘ê·¼ì´ ê±°ë¶€ë˜ì—ˆìŠµë‹ˆë‹¤" + scopeUser: "ë‹¤ìŒ ì‚¬ìš©ìžë¡œ 활ë™í•˜ê³ 있습니다." pleaseLogin: "어플리케ì´ì…˜ì˜ ì ‘ê·¼ì„ í—ˆê°€í•˜ë ¤ë©´ 로그ì¸í•˜ì‹ì‹œì˜¤." + byClickingYouWillBeRedirectedToThisUrl: "ì ‘ê·¼ì„ í—ˆìš©í•˜ë©´ ìžë™ìœ¼ë¡œ ë‹¤ìŒ URL로 ì´ë™í•©ë‹ˆë‹¤." _antennaSources: all: "ëª¨ë“ ë…¸íŠ¸" homeTimeline: "íŒ”ë¡œìš°ì¤‘ì¸ ìœ ì €ì˜ ë…¸íŠ¸" @@ -2710,3 +2735,12 @@ _embedCodeGen: generateCode: "ìž„ë² ë””ë“œ 코드를 만들기" codeGenerated: "코드를 만들었습니다." codeGeneratedDescription: "만들어진 코드를 웹 사ì´íŠ¸ì— ë¶™ì—¬ì„œ 사용하세요." +_selfXssPrevention: + warning: "ê²½ê³ " + title: "â€œì´ í™”ë©´ì— ë”가를 붙여넣어ë¼\"는 ê²ƒì€ ëª¨ë‘ ì‚¬ê¸°ìž…ë‹ˆë‹¤." + description1: "ì—¬ê¸°ì— ë¬´ì–¸ê°€ë¥¼ 붙여넣으면 ì•…ì˜ì ì¸ ì‚¬ìš©ìžì—게 ê³„ì •ì„ íƒˆì·¨ë‹¹í•˜ê±°ë‚˜ ê°œì¸ì •보를 ë„ìš©ë‹¹í• ìˆ˜ 있습니다." + description2: "붙여 ë„£ìœ¼ë ¤ëŠ” í•ëª©ì´ ë¬´ì—‡ì¸ì§€ ì •í™•ížˆ ì´í•´í•˜ì§€ 못하는 경우, %c지금 바로 ìž‘ì—…ì„ ì¤‘ë‹¨í•˜ê³ ì´ ì°½ì„ ë‹«ìœ¼ì‹ì‹œì˜¤." + description3: "ìžì„¸í•œ ë‚´ìš©ì€ ì—¬ê¸°ë¥¼ 확ì¸í•´ 주세요. {link}" +_followRequest: + recieved: "ë°›ì€ ì‹ ì²" + sent: "보낸 ì‹ ì²" diff --git a/locales/lo-LA.yml b/locales/lo-LA.yml index b100d0300f..38965119fe 100644 --- a/locales/lo-LA.yml +++ b/locales/lo-LA.yml @@ -299,7 +299,6 @@ enableLocalTimeline: "ເປີດໃຊ້ທາມລາàºàº—້àºàº‡àº–ິà enableGlobalTimeline: "ເປີດໃຊ້ທາມລາàºàº—ົ່ວໂລàº" disablingTimelinesInfo: "ຜູ້ດູà»àº¥àº¥àº°àºšàºšà»àº¥àº°àºœàº¹à»‰àº„ວບຄຸມຈະສາມາດເຂົ້າເຖີງໄທມ໌ໄລນ໌ທັ້ງເບີດ ເຖີງວ່າຈະບà»à»ˆà»„ດ້ເປີດໃຊ້ງານàºà»à»ˆàº•າມ" registration: "ລົງທະບຽນ" -enableRegistration: "ເປີດໃຊ້àºàº²àº™àº¥àº»àº‡àº—ະບຽນຜູ້ໃຊ້ໃà»à»ˆ" invite: "ເຊີນ" driveCapacityPerLocalAccount: "ຄວາມຈຸຂàºàº‡ drive ຕà»à»ˆàºœàº¹à»‰à»ƒàºŠà»‰àº—້àºàº‡àº–ິ່ນ" driveCapacityPerRemoteAccount: "ຄວາມຈຸຂàºàº‡ drive ຕà»à»ˆàºœàº¹à»‰à»ƒàºŠà»‰à»„ລàºàº°à»„àº" diff --git a/locales/nl-NL.yml b/locales/nl-NL.yml index dde3035357..7e5e9cbbfb 100644 --- a/locales/nl-NL.yml +++ b/locales/nl-NL.yml @@ -333,7 +333,6 @@ enableLocalTimeline: "Inschakelen lokale tijdlijn" enableGlobalTimeline: "Inschakelen globale tijdlijn " disablingTimelinesInfo: "Beheerders en moderators hebben altijd toegang tot alle tijdlijnen, ook als ze niet actief zijn." registration: "Registreren" -enableRegistration: "Inschakelen registratie nieuwe gebruikers " invite: "Uitnodigen" driveCapacityPerLocalAccount: "Opslagruimte per lokale gebruiker" driveCapacityPerRemoteAccount: "Opslagruimte per externe gebruiker" diff --git a/locales/no-NO.yml b/locales/no-NO.yml index c5f61db745..87ea01764d 100644 --- a/locales/no-NO.yml +++ b/locales/no-NO.yml @@ -260,7 +260,6 @@ enableLocalTimeline: "Aktiver lokal tidslinje" enableGlobalTimeline: "Aktiver global tidslinje" disablingTimelinesInfo: "Administratorer og Moderatorer vil alltid ha tilgang til alle tidslinjer, selv om de ikke er aktivert." registration: "Registrer" -enableRegistration: "Aktiver registrering av nye brukere" invite: "Inviter" basicInfo: "Grunnleggende informasjon" pinnedUsers: "Festede brukrere" diff --git a/locales/pl-PL.yml b/locales/pl-PL.yml index d7afd57760..203f44b334 100644 --- a/locales/pl-PL.yml +++ b/locales/pl-PL.yml @@ -362,7 +362,6 @@ enableLocalTimeline: "Włącz lokalnÄ… oÅ› czasu" enableGlobalTimeline: "Włącz globalnÄ… oÅ› czasu" disablingTimelinesInfo: "Administratorzy i moderatorzy bÄ™dÄ… zawsze mieć dostÄ™p do wszystkich osi czasu, nawet gdy sÄ… one wyłączone." registration: "Zarejestruj siÄ™" -enableRegistration: "Włącz rejestracjÄ™ nowych użytkowników" invite: "ZaproÅ›" driveCapacityPerLocalAccount: "Powierzchnia dyskowa na lokalnego użytkownika" driveCapacityPerRemoteAccount: "Powierzchnia dyskowa na zdalnego użytkownika" @@ -492,6 +491,10 @@ uiLanguage: "JÄ™zyk wyÅ›wietlania UI" aboutX: "O {x}" emojiStyle: "Styl emoji" native: "Natywny" +menuStyle: "Styl Menu" +style: "Styl" +drawer: "Schowek" +popup: "WyskakujÄ…ce okienka" showNoteActionsOnlyHover: "Pokazuj akcje notatek tylko po najechaniu myszkÄ…" showReactionsCount: "WyÅ›wietl liczbÄ™ reakcji na notatkÄ™" noHistory: "Brak historii" @@ -574,6 +577,7 @@ ascendingOrder: "RosnÄ…co" descendingOrder: "MalejÄ…co" scratchpad: "Brudnopis" scratchpadDescription: "Brudnopis zawiera eksperymentalne Å›rodowisko dla AiScript. Możesz pisać, wykonywać i sprawdzać wyniki w interakcji z Misskey." +uiInspector: "Inspektor UI" output: "WyjÅ›cie" script: "Skrypt" disablePagesScript: "Wyłącz AiScript na Stronach" @@ -654,6 +658,7 @@ smtpSecure: "Użyj niejawnego SSL/TLS dla połączeÅ„ SMTP" smtpSecureInfo: "Wyłącz, jeżeli używasz STARTTLS" testEmail: "Przetestuj dostarczanie wiadomoÅ›ci e-mail" wordMute: "Wyciszenie sÅ‚owa" +hardWordMute: "Wyciszaj przekleÅ„stwa" regexpError: "Błąd wyrażenia regularnego" regexpErrorDescription: "WystÄ…piÅ‚ błąd w wyrażeniu regularnym w linii {line} twoich {tab} wyciszeÅ„:" instanceMute: "Wyciszone instancje" @@ -826,6 +831,7 @@ administration: "ZarzÄ…dzanie" accounts: "Konta" switch: "Przełącz" noMaintainerInformationWarning: "Informacje o administratorze nie sÄ… skonfigurowane." +noInquiryUrlWarning: "Adres URL zapytania nie zostaÅ‚ ustawiony" noBotProtectionWarning: "Zabezpieczenie przed botami nie jest skonfigurowane." configure: "Skonfiguruj" postToGallery: "Opublikuj w galerii" @@ -890,6 +896,7 @@ followersVisibility: "Widoczność obserwujÄ…cych" continueThread: "Pokaż kontynuacjÄ™ wÄ…tku" deleteAccountConfirm: "Spowoduje to nieodwracalne usuniÄ™cie Twojego konta. Kontynuować?" incorrectPassword: "NieprawidÅ‚owe hasÅ‚o." +incorrectTotp: "HasÅ‚o pojedynczego użytku jest nie poprawne, lub straciÅ‚o ważność" voteConfirm: "Potwierdzić swój gÅ‚os na \"{choice}\"?" hide: "Ukryj" useDrawerReactionPickerForMobile: "WyÅ›wietlaj wybornik reakcji jako szufladÄ™ na urzÄ…dzeniach mobilnych" @@ -914,6 +921,10 @@ oneHour: "1 godzina" oneDay: "1 dzieÅ„" oneWeek: "1 tydzieÅ„" oneMonth: "jeden miesiÄ…c" +threeMonths: "3 miesiÄ…ce" +oneYear: "Rok" +threeDays: "3 dni" +reflectMayTakeTime: "Może minąć trochÄ™ czasu, zanim bÄ™dzie to uwzglÄ™dnione" failedToFetchAccountInformation: "Nie udaÅ‚o siÄ™ uzyskać informacji o koncie" rateLimitExceeded: "Limit szybkoÅ›ci przekroczony" cropImage: "Przytnij obraz" @@ -924,9 +935,11 @@ file: "Pliki" recentNHours: "W ciÄ…gu ostatnich {n} godzin" recentNDays: "W ciÄ…gu ostatnich {n} dni" noEmailServerWarning: "Serwer Email nie jest skonfigurowany" +thereIsUnresolvedAbuseReportWarning: "IstniejÄ… niewyjaÅ›nione raporty" recommended: "Zalecane" check: "Zweryfikuj" driveCapOverrideLabel: "ZmieÅ„ limit pojemnoÅ›ci dysku użytkownika" +driveCapOverrideCaption: "Resetuje pojemność do wartoÅ›ci domyÅ›lnej, przez wpisanie wartoÅ›ci 0 lub niższej" requireAdminForView: "Aby to zobaczyć, musisz być administratorem" isSystemAccount: "To jest konto stworzone i zarzÄ…dzane przez system" typeToConfirm: "Wprowadź {x}, aby potwierdzić" @@ -995,17 +1008,29 @@ unassign: "Cofnij przydzielenie" color: "Kolor" manageCustomEmojis: "ZarzÄ…dzaj niestandardowymi Emoji" manageAvatarDecorations: "ZarzÄ…dzaj dekoracjami awatara" +youCannotCreateAnymore: "Limit kreacji zostaÅ‚ przekroczony" +cannotPerformTemporary: "Opcja tymczasowo niedostÄ™pna" +cannotPerformTemporaryDescription: "Ta akcja nie może zostać wykonana, z powodu przekroczenia limitu wykonaÅ„. Prosimy poczekać chwilÄ™ i spróbować ponownie" invalidParamError: "Błąd parametrów" +invalidParamErrorDescription: "WartoÅ›ci, które zostaÅ‚y podane sÄ… niepoprawne. Zwykle jest to spowodowane bugiem, lecz również może być to spowodowane przekroczeniem limitu wartoÅ›ci, lub podobnym problemem" permissionDeniedError: "Odrzucono operacje" permissionDeniedErrorDescription: "Konto nie posiada uprawnieÅ„" preset: "Konfiguracja" selectFromPresets: "Wybierz konfiguracje" achievements: "OsiÄ…gniÄ™cia" +gotInvalidResponseError: "Niepoprawna odpowiedź serwera" +gotInvalidResponseErrorDescription: "WystÄ…piÅ‚ problem z Twoim połączeniem z Internetem, lub z serwerem. {Spróbuj ponownie} wkrótce." +thisPostMayBeAnnoying: "Ten wpis może obrażać pozostaÅ‚ych użytkowników" +thisPostMayBeAnnoyingHome: "Opublikuj na domowej osi czasu" thisPostMayBeAnnoyingCancel: "Odrzuć" +thisPostMayBeAnnoyingIgnore: "Zignoruj i wyÅ›lij" +collapseRenotes: "ZwiÅ„ wpisy, które już zobaczyÅ‚eÅ›" +collapseRenotesDescription: "ZwiÅ„ wpisy, na które już zareagowaÅ‚eÅ› lub udostÄ™pniÅ‚eÅ›" internalServerError: "WewnÄ™trzny błąd serwera" internalServerErrorDescription: "Niespodziewany błąd po stronie serwera" copyErrorInfo: "Kopiuj informacje o błędzie" joinThisServer: "Dołącz do chaty" +exploreOtherServers: "Szukaj innej instancji" disableFederationOk: "Wyłącz federacje" invitationRequiredToRegister: "Ten serwer wymaga zaproszenia. Tylko osoby z zaproszeniem mogÄ… siÄ™ zarejestrować" emailNotSupported: "WysyÅ‚anie wiadomoÅ›ci E-mail nie jest obsÅ‚ugiwane na tym serwerze" diff --git a/locales/pt-PT.yml b/locales/pt-PT.yml index 9039fd2141..7ef9e3a946 100644 --- a/locales/pt-PT.yml +++ b/locales/pt-PT.yml @@ -376,7 +376,6 @@ enableLocalTimeline: "Ativar linha do tempo local" enableGlobalTimeline: "Ativar linha do tempo global" disablingTimelinesInfo: "Se você desabilitar essas linhas do tempo, administradores e moderadores ainda poderão usá-las por conveniência." registration: "Registar" -enableRegistration: "Permitir que qualquer pessoa se registre" invite: "Convidar" driveCapacityPerLocalAccount: "Capacidade do drive por usuário local" driveCapacityPerRemoteAccount: "Capacidade do drive por usuário remoto" diff --git a/locales/ro-RO.yml b/locales/ro-RO.yml index 3cc09aa5c2..71dc1dc94c 100644 --- a/locales/ro-RO.yml +++ b/locales/ro-RO.yml @@ -341,7 +341,6 @@ enableLocalTimeline: "Activează cronologia locală" enableGlobalTimeline: "Activeaza cronologia globală" disablingTimelinesInfo: "Administratorii È™i Moderatorii vor avea mereu access la toate cronologiile, chiar dacă nu sunt activate." registration: "Inregistrare" -enableRegistration: "Activează înregistrările pentru utilizatori noi" invite: "Invită" driveCapacityPerLocalAccount: "Capacitatea Drive-ului per utilizator local" driveCapacityPerRemoteAccount: "Capacitatea Drive-ului per utilizator extern" diff --git a/locales/ru-RU.yml b/locales/ru-RU.yml index 70178ec2fd..537e99036c 100644 --- a/locales/ru-RU.yml +++ b/locales/ru-RU.yml @@ -8,6 +8,9 @@ search: "ПоиÑк" notifications: "УведомлениÑ" username: "Ð˜Ð¼Ñ Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ" password: "Пароль" +initialPasswordForSetup: "Пароль Ð´Ð»Ñ Ð½Ð°Ñ‡Ð°Ð»Ð° наÑтройки" +initialPasswordIsIncorrect: "Пароль Ð´Ð»Ñ Ð·Ð°Ð¿ÑƒÑка наÑтройки неверен" +initialPasswordForSetupDescription: "ЕÑли вы уÑтановили Misskey ÑамоÑтоÑтельно, иÑпользуйте пароль, который вы указали в файле конфигурации.\nЕÑли вы иÑпользуете что-то вроде хоÑтинга Misskey, иÑпользуйте предоÑтавленный пароль.\nЕÑли вы не уÑтановили пароль, оÑтавьте его пуÑтым и продолжайте." forgotPassword: "Забыли пароль?" fetchingAsApObject: "Приём Ñ Ð´Ñ€ÑƒÐ³Ð¸Ñ… Ñайтов" ok: "Подтвердить" @@ -232,6 +235,7 @@ clearCachedFilesConfirm: "Удалить вÑе закÑшированные фРblockedInstances: "Заблокированные инÑтанÑÑ‹" blockedInstancesDescription: "Введите ÑпиÑок инÑтанÑов, которые хотите заблокировать. Они больше не Ñмогут обмениватьÑÑ Ñ Ð²Ð°ÑˆÐ¸Ð¼ инÑтанÑом." silencedInstances: "Заглушённые инÑтанÑÑ‹" +federationAllowedHosts: "Серверы, поддерживающие федерацию" muteAndBlock: "Скрытие и блокировка" mutedUsers: "Скрытые пользователи" blockedUsers: "Заблокированные пользователи" @@ -330,6 +334,7 @@ renameFolder: "Переименовать папку" deleteFolder: "Удалить папку" folder: "Папка" addFile: "Добавить файл" +showFile: "ПоÑмотреть файл" emptyDrive: "ДиÑк пуÑÑ‚" emptyFolder: "Папка пуÑта" unableToDelete: "Удаление невозможно" @@ -372,7 +377,6 @@ enableLocalTimeline: "Включить локальную ленту" enableGlobalTimeline: "Включить глобальную ленту" disablingTimelinesInfo: "У админиÑтраторов и модераторов еÑть доÑтуп ко вÑем лентам, даже еÑли они отключены." registration: "РегиÑтрациÑ" -enableRegistration: "Разрешить региÑтрацию" invite: "ПриглаÑить" driveCapacityPerLocalAccount: "Объём ДиÑка на одного локального пользователÑ" driveCapacityPerRemoteAccount: "Объём ДиÑка на одного Ð¿Ð¾Ð»ÑŒÐ·Ð¾Ð²Ð°Ñ‚ÐµÐ»Ñ Ñ Ð´Ñ€ÑƒÐ³Ð¾Ð³Ð¾ ÑкземплÑра" @@ -443,6 +447,7 @@ totp: "Приложение-аутентификатор" totpDescription: "ОпиÑание приложениÑ-аутентификатора" moderator: "Модератор" moderation: "МодерациÑ" +moderationNote: "ÐŸÑ€Ð¸Ð¼ÐµÑ‡Ð°Ð½Ð¸Ñ Ð¼Ð¾Ð´ÐµÑ€Ð°Ñ‚Ð¾Ñ€Ð°" moderationLogs: "Журнал модерации" nUsersMentioned: "УпомÑнуло пользователей: {n}" securityKeyAndPasskey: "Ключ безопаÑноÑти и Ð¿Ð°Ñ€Ð¾Ð»ÑŒÐ½Ð°Ñ Ñ„Ñ€Ð°Ð·Ð°" @@ -503,6 +508,8 @@ uiLanguage: "Язык интерфейÑа" aboutX: "ОпиÑание {x}" emojiStyle: "Стиль Ñмодзи" native: "СиÑтемные" +menuStyle: "Стиль меню" +style: "Стиль" showNoteActionsOnlyHover: "Показывать кнопки у заметок только при наведении" showReactionsCount: "Видеть количеÑтво реакций на заметках" noHistory: "ИÑÑ‚Ð¾Ñ€Ð¸Ñ Ð¿Ð¾ÐºÐ° пуÑта" diff --git a/locales/sk-SK.yml b/locales/sk-SK.yml index 60ce45a6b9..f3f43ee6a6 100644 --- a/locales/sk-SK.yml +++ b/locales/sk-SK.yml @@ -331,7 +331,6 @@ enableLocalTimeline: "PovoliÅ¥ lokálnu Äasovú os" enableGlobalTimeline: "PovoliÅ¥ globálnu Äasovú os" disablingTimelinesInfo: "Administrátori a moderátori majú vždy prÃstup ku vÅ¡etkým Äasovým osiam, aj keÄ sú vypnuté." registration: "Registrácia" -enableRegistration: "PovoliÅ¥ registráciu nových použÃvateľov" invite: "PozvaÅ¥" driveCapacityPerLocalAccount: "Kapacita disku pre použÃvateľa" driveCapacityPerRemoteAccount: "Kapacita disku pre vzdialeného použÃvateľa" diff --git a/locales/sv-SE.yml b/locales/sv-SE.yml index 5a0de660e8..5961605645 100644 --- a/locales/sv-SE.yml +++ b/locales/sv-SE.yml @@ -333,7 +333,6 @@ disconnectService: "Koppla frÃ¥n" enableLocalTimeline: "Aktivera lokal tidslinje" enableGlobalTimeline: "Aktivera global tidslinje" registration: "Registrera" -enableRegistration: "Aktivera registrering av nya användare" invite: "Inbjudan" inMb: "I megabyte" bannerUrl: "URL till banner-bilden" @@ -385,6 +384,7 @@ passwordLessLoginDescription: "TillÃ¥ter lösenordsfri inloggning med endast en resetPassword: "Ã…terställ Lösenord" newPasswordIs: "Det nya lösenordet är \"{password}\"" share: "Dela" +markAsReadAllTalkMessages: "Markera alla meddelanden som lästa" help: "Hjälp" close: "Stäng" invites: "Inbjudan" @@ -393,12 +393,15 @@ transfer: "Överför" text: "Text" enable: "Aktivera" next: "Nästa" +retype: "Ange igen" +noMessagesYet: "Inga meddelanden än" invitations: "Inbjudan" invitationCode: "Inbjudningskod" available: "Tillgängligt" weakPassword: "Svagt Lösenord" normalPassword: "Medel Lösenord" strongPassword: "Starkt Lösenord" +signinWith: "Logga in med {x}" signinFailed: "Kan inte logga in. Det angivna användarnamnet eller lösenordet är felaktigt." or: "eller" language: "SprÃ¥k" @@ -410,70 +413,124 @@ existingAccount: "Existerande konto" regenerate: "Regenerera" fontSize: "Textstorlek" openImageInNewTab: "Öppna bild i ny flik" +appearance: "Utseende" clientSettings: "Klientinställningar" accountSettings: "Kontoinställningar" numberOfDays: "Antal dagar" +objectStorageUseSSL: "Använd SSL" +serverLogs: "Serverloggar" deleteAll: "Radera alla" sounds: "Ljud" sound: "Ljud" listen: "Lyssna" none: "Ingen" volume: "Volym" +notUseSound: "Inaktivera ljud" chooseEmoji: "Välj en emoji" recentUsed: "Senast använd" install: "Installera" uninstall: "Avinstallera" +deleteAllFiles: "Radera alla filer" +deleteAllFilesConfirm: "Är du säker pÃ¥ att du vill radera alla filer?" menu: "Meny" +addItem: "Lägg till objekt" serviceworkerInfo: "MÃ¥ste vara aktiverad för pushnotiser." enableInfiniteScroll: "Ladda mer automatiskt" enablePlayer: "Öppna videospelare" +description: "Beskrivning" permission: "Behörigheter" enableAll: "Aktivera alla" +disableAll: "Inaktivera alla" edit: "Ändra" enableEmail: "Aktivera epost-utskick" email: "E-post" +emailAddress: "E-postadress" smtpHost: "Värd" smtpUser: "Användarnamn" smtpPass: "Lösenord" emptyToDisableSmtpAuth: "Lämna användarnamn och lösenord tomt för att avaktivera SMTP verifiering" +makeActive: "Aktivera" +copy: "Kopiera" +overview: "Översikt" logs: "Logg" +database: "Databas" channel: "kanal" create: "Skapa" other: "Mer" +abuseReports: "Rapporter" +reportAbuse: "Rapporter" +reportAbuseOf: "Rapportera {name}" +abuseReported: "Din rapport har skickats. Tack sÃ¥ mycket." send: "Skicka" openInNewTab: "Öppna i ny flik" createNew: "Skapa ny" +private: "Privat" i18nInfo: "Misskey översätts till mÃ¥nga olika sprÃ¥k av volontärer. Du kan hjälpa till med översättningen pÃ¥ {link}." accountInfo: "Kontoinformation" +followersCount: "Antal följare" +yes: "Ja" +no: "Nej" clips: "Klipp" duplicate: "Duplicera" reloadToApplySetting: "Inställningen tillämpas efter sidan laddas om. Vill du göra det nu?" clearCache: "Rensa cache" onlineUsersCount: "{n} användare är online" +nUsers: "{n} användare" nNotes: "{n} Noter" backgroundColor: "Bakgrundsbild" textColor: "Text" +saveAs: "Spara som..." +saveConfirm: "Spara ändringar?" youAreRunningUpToDateClient: "Klienten du använder är uppdaterat." newVersionOfClientAvailable: "Ny version av klienten är tillgänglig." +editCode: "Redigera kod" publish: "Publicera" typingUsers: "{users} skriver" +goBack: "Tillbaka" +addDescription: "Lägg till beskrivning" info: "Om" +online: "Online" +active: "Aktiv" +offline: "Offline" enabled: "Aktiverad" +quickAction: "SnabbÃ¥tgärder" user: "Användare" +gallery: "Galleri" +popularPosts: "Populära inlägg" customCssWarn: "Den här inställningen borde bara ändrats av en som har rätta kunskaper. Om du ställer in det här fel sÃ¥ kan klienten sluta fungera rätt." global: "Global" squareAvatars: "Visa fyrkantiga profilbilder" sent: "Skicka" +searchResult: "Sökresultat" +learnMore: "Läs mer" misskeyUpdated: "Misskey har uppdaterats!" +translate: "Översätt" +controlPanel: "Kontrollpanel" +manageAccounts: "Hantera konton" incorrectPassword: "Fel lösenord." +hide: "Dölj" welcomeBackWithName: "Välkommen tillbaka, {name}" clickToFinishEmailVerification: "Tryck pÃ¥ [{ok}] för att slutföra bekräftelsen pÃ¥ e-postadressen." +size: "Storlek" searchByGoogle: "Sök" +indefinitely: "Aldrig" +tenMinutes: "10 minuter" +oneHour: "En timme" +oneDay: "En dag" +oneWeek: "En vecka" +oneMonth: "En mÃ¥nad" +threeMonths: "3 mÃ¥nader" +oneYear: "1 Ã¥r" +threeDays: "3 dagar" file: "Filer" +deleteAccount: "Radera konto" +label: "Etikett" cannotUploadBecauseNoFreeSpace: "Kan inte ladda upp filen för att det finns inget lagringsutrymme kvar." cannotUploadBecauseExceedsFileSizeLimit: "Kan inte ladda upp filen för att den är större än filstorleksgränsen." +beta: "Beta" enableAutoSensitive: "Automatisk NSFW markering" enableAutoSensitiveDescription: "TillÃ¥ter automatiskt detektering och marketing av NSFW media genom Maskininlärning när möjligt. Även om denna inställningen är avaktiverad, kan det vara aktiverat pÃ¥ hela instansen." +move: "Flytta" pushNotification: "Pushnotiser" subscribePushNotification: "Aktivera pushnotiser" unsubscribePushNotification: "Avaktivera pushnotiser" @@ -482,38 +539,86 @@ pushNotificationNotSupported: "Din webbläsare eller instans har inte stöd för windowMaximize: "Maximera" windowMinimize: "Minimera" windowRestore: "Ã…terställ" +tools: "Verktyg" +like: "Gilla" pleaseDonate: "Misskey är en gratis programvara som används pÃ¥ {host}. Donera gärna för att göra utvecklingen ständigt, tack!" +roles: "Roll" +role: "Roll" +color: "Färg" resetPasswordConfirm: "Ã…terställ verkligen ditt lösenord?" dataSaver: "Databesparing" icon: "Profilbild" +forYou: "För dig" replies: "Svara" renotes: "Omnotera" +loadReplies: "Visa svar" +loadConversation: "Visa konversation" +authentication: "Autentisering" +sourceCode: "Källkod" +doReaction: "Lägg till reaktion" +code: "Kod" +gameRetry: "Försök igen" +inquiry: "Kontakt" +tryAgain: "Försök igen senare" +signinWithPasskey: "Logga in med nyckel" +unknownWebAuthnKey: "Okänd nyckel" _delivery: stop: "Suspenderad" _type: none: "Publiceras" +_initialAccountSetting: + profileSetting: "Profilinställningar" +_initialTutorial: + _reaction: + title: "Vad är reaktioner?" _achievements: _types: _open3windows: title: "Flera Fönster" description: "Ha minst 3 fönster öppna samtidigt" +_role: + edit: "Redigera roll" _ffVisibility: public: "Publicera" + private: "Privat" +_accountDelete: + accountDelete: "Radera konto" +_ad: + back: "Tillbaka" +_gallery: + like: "Gilla" _email: _follow: title: "följde dig" +_aboutMisskey: + source: "Källkod" + projectMembers: "Projektmedlemmar" _channel: setBanner: "Välj banner" removeBanner: "Ta bort banner" + nameAndDescription: "Namn och beskrivning" +_menuDisplay: + hide: "Dölj" _theme: + description: "Beskrivning" + color: "Färg" keys: mention: "Nämn" renote: "Omnotera" _sfx: note: "Noter" notification: "Notifikationer" +_ago: + justNow: "Just nu" _2fa: + step3Title: "Ange en autentiseringskod" renewTOTPCancel: "Nej tack" +_permissions: + "read:reactions": "Visa dina reaktioner" + "write:reactions": "Redigera dina reaktioner" + "write:admin:delete-account": "Radera användarkonto" + "write:admin:roles": "Hantera roller" + "read:admin:roles": "Visa roller" _antennaSources: all: "Alla noter" homeTimeline: "Noter frÃ¥n följda användare" @@ -530,13 +635,19 @@ _widgets: _userList: chooseList: "Välj lista" _cw: + hide: "Dölj" show: "Ladda mer" + chars: "{count} tecken" + files: "{count} fil(er)" +_poll: + infinite: "Aldrig" _visibility: home: "Hem" followers: "Följare" _profile: name: "Namn" username: "Användarnamn" + metadataLabel: "Etikett" changeAvatar: "Ändra profilbild" changeBanner: "Ändra banner" _exportOrImport: @@ -547,9 +658,12 @@ _exportOrImport: userLists: "Listor" _charts: federation: "Federation" + activeUsers: "Aktiva användare" _timelines: home: "Hem" global: "Global" +_play: + summary: "Beskrivning" _pages: blocks: image: "Bilder" @@ -567,6 +681,8 @@ _notification: reply: "Svara" renote: "Omnotera" _deck: + addColumn: "Lägg till kolumn" + deleteProfile: "Radera profil" _columns: notifications: "Notifikationer" tl: "Tidslinje" @@ -584,3 +700,10 @@ _abuseReport: _moderationLogTypes: suspend: "Suspendera" resetPassword: "Ã…terställ Lösenord" +_reversi: + blackOrWhite: "Svart/Vit" + rules: "Regler" + black: "Svart" + white: "Vit" +_selfXssPrevention: + warning: "VARNING" diff --git a/locales/th-TH.yml b/locales/th-TH.yml index c70d448e2b..c725282d50 100644 --- a/locales/th-TH.yml +++ b/locales/th-TH.yml @@ -8,6 +8,9 @@ search: "ค้นหา" notifications: "เเจ้งเตืà¸à¸™" username: "ชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰" password: "รหัสผ่าน" +initialPasswordForSetup: "รหัสผ่านเริ่มต้นสำหรับà¸à¸²à¸£à¸•ั้งค่า" +initialPasswordIsIncorrect: "รหัสผ่านเริ่มต้นสำหรับตั้งค่านั้นไม่ถูà¸à¸•้à¸à¸‡à¸„่ะ" +initialPasswordForSetupDescription: "ถ้าหาà¸à¸„ุณติดตั้ง Misskey เà¸à¸‡ ให้ใช้รหัสผ่านที่คุณป้à¸à¸™à¹ƒà¸™à¹„ฟล์à¸à¸³à¸«à¸™à¸”ค่า \nถ้าหาà¸à¸„ุณà¸à¸³à¸¥à¸±à¸‡à¹ƒà¸Šà¹‰à¸šà¸£à¸´à¸à¸²à¸£à¹‚ฮสต์ Misskey ให้ใช้รหัสผ่านที่ได้รับมา\nถ้ายังไม่มีรหัสผ่าน ให้ข้ามช่à¸à¸‡à¸£à¸«à¸±à¸ªà¸œà¹ˆà¸²à¸™à¹„ป à¹à¸¥à¹‰à¸§à¸à¸”ต่à¸à¹„ป" forgotPassword: "ลืมรหัสผ่าน" fetchingAsApObject: "à¸à¸³à¸¥à¸±à¸‡à¸”ึงข้à¸à¸¡à¸¹à¸¥à¸ˆà¸²à¸à¸ªà¸«à¸žà¸±à¸™à¸˜à¹Œ..." ok: "ตà¸à¸¥à¸‡" @@ -236,6 +239,8 @@ silencedInstances: "ปิดปาà¸à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸™à¸µà¹‰ silencedInstancesDescription: "ระบุโฮสต์ขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸—ี่ต้à¸à¸‡à¸à¸²à¸£à¸›à¸´à¸”ปาภคั่นด้วยà¸à¸²à¸£à¸‚ึ้นบรรทัดใหม่, บัà¸à¸Šà¸µà¸—ั้งหมดขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸ˆà¸°à¸–ืà¸à¸§à¹ˆà¸²à¸–ูà¸à¸›à¸´à¸”ปาà¸à¹€à¸Šà¹ˆà¸™à¸à¸±à¸™ ทำได้เฉพาะคำขà¸à¸•ิดตามเท่านั้น à¹à¸¥à¸°à¹„ม่สามารถà¸à¸¥à¹ˆà¸²à¸§à¸–ึงบัà¸à¸Šà¸µà¹ƒà¸™à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸™à¸µà¹‰à¹„ด้หาà¸à¹„ม่ได้ถูà¸à¸•ิดตามà¸à¸¥à¸±à¸š | สิ่งนี้ไม่มีผลต่à¸à¸à¸´à¸™à¸ªà¹à¸•นซ์ที่ถูà¸à¸šà¸¥à¹‡à¸à¸" mediaSilencedInstances: "เซิร์ฟเวà¸à¸£à¹Œà¸—ี่ถูà¸à¸›à¸´à¸”ปาà¸à¸ªà¸·à¹ˆà¸" mediaSilencedInstancesDescription: "ระบุโฮสต์ขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸—ี่ต้à¸à¸‡à¸à¸²à¸£à¸›à¸´à¸”ปาà¸à¸ªà¸·à¹ˆà¸ คั่นด้วยà¸à¸²à¸£à¸‚ึ้นบรรทัดใหม่, ไฟล์ที่ถูà¸à¸ªà¹ˆà¸‡à¸ˆà¸²à¸à¸šà¸±à¸à¸Šà¸µà¸‚à¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸”ังà¸à¸¥à¹ˆà¸²à¸§à¸ˆà¸°à¸–ืà¸à¸§à¹ˆà¸²à¸–ูà¸à¸›à¸´à¸”ปาภà¹à¸¥à¹‰à¸§à¸ˆà¸°à¸–ูà¸à¸•ิดเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¸§à¹ˆà¸²à¸¡à¸µà¹€à¸™à¸·à¹‰à¸à¸«à¸²à¸¥à¸°à¹€à¸à¸µà¸¢à¸”à¸à¹ˆà¸à¸™ à¹à¸¥à¸°à¹€à¸à¹‚มจิà¹à¸šà¸šà¸à¸³à¸«à¸™à¸”เà¸à¸‡à¸à¹‡à¸ˆà¸°à¹ƒà¸Šà¹‰à¹„ม่ได้ด้วย | สิ่งนี้ไม่มีผลต่à¸à¸à¸´à¸™à¸ªà¹à¸•นซ์ที่ถูà¸à¸šà¸¥à¹‡à¸à¸" +federationAllowedHosts: "เซิร์ฟเวà¸à¸£à¹Œà¸—ี่เปิดให้บริà¸à¸²à¸£à¹à¸šà¸šà¹€à¸Ÿà¹€à¸”à¸à¹€à¸£à¸Šà¸±à¹ˆà¸™" +federationAllowedHostsDescription: "ระบุชื่à¸à¹‚ฮสต์ขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸—ี่คุณต้à¸à¸‡à¸à¸²à¸£à¸à¸™à¸¸à¸à¸²à¸•ให้เชื่à¸à¸¡à¸•่à¸à¹à¸šà¸šà¹€à¸Ÿà¹€à¸”à¸à¹€à¸£à¸Šà¸±à¹ˆà¸™ โดยต้à¸à¸‡à¹€à¸§à¹‰à¸™à¸§à¸£à¸£à¸„à¹à¸•่ละบรรทัด" muteAndBlock: "ปิดเสียงà¹à¸¥à¸°à¸šà¸¥à¹‡à¸à¸" mutedUsers: "ผู้ใช้ที่ถูà¸à¸›à¸´à¸”เสียง" blockedUsers: "ผู้ใช้ที่ถูà¸à¸šà¸¥à¹‡à¸à¸" @@ -334,6 +339,7 @@ renameFolder: "เปลี่ยนชื่à¸à¹‚ฟลเดà¸à¸£à¹Œ" deleteFolder: "ลบโฟลเดà¸à¸£à¹Œ" folder: "โฟลเดà¸à¸£à¹Œ" addFile: "เพิ่มไฟล์" +showFile: "à¹à¸ªà¸”งไฟล์" emptyDrive: "ไดรฟ์ขà¸à¸‡à¸„ุณว่างเปล่านะ" emptyFolder: "โฟลเดà¸à¸£à¹Œà¸™à¸µà¹‰à¸§à¹ˆà¸²à¸‡à¹€à¸›à¸¥à¹ˆà¸²" unableToDelete: "ไม่สามารถลบà¸à¸à¸à¹„ด้" @@ -376,7 +382,6 @@ enableLocalTimeline: "เปิดใช้งานไทม์ไลน์ทà enableGlobalTimeline: "เปิดใช้งานไทม์ไลน์ทั่วโลà¸" disablingTimelinesInfo: "ผู้ดูà¹à¸¥à¸£à¸°à¸šà¸šà¹à¸¥à¸°à¸œà¸¹à¹‰à¸„วบคุมจะสามารถเข้าถึงไทม์ไลน์ทั้งหมด ถึงà¹à¸¡à¹‰à¸§à¹ˆà¸²à¸ˆà¸°à¹„ม่ได้เปิดใช้งานà¸à¹‡à¸•าม" registration: "ลงทะเบียน" -enableRegistration: "เปิดใช้งานà¸à¸²à¸£à¸¥à¸‡à¸—ะเบียนผู้ใช้ใหม่" invite: "คำเชิà¸" driveCapacityPerLocalAccount: "ความจุขà¸à¸‡à¹„ดรฟ์ต่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸—้à¸à¸‡à¸–ิ่น" driveCapacityPerRemoteAccount: "ความจุขà¸à¸‡à¹„ดรฟ์ต่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸£à¸°à¸¢à¸°à¹„à¸à¸¥" @@ -448,6 +453,7 @@ totpDescription: "ใช้à¹à¸à¸›à¸¢à¸·à¸™à¸¢à¸±à¸™à¸•ัวตนเพื๠moderator: "ผู้ควบคุม" moderation: "à¸à¸²à¸£à¸à¸¥à¸±à¹ˆà¸™à¸à¸£à¸à¸‡" moderationNote: "โน้ตà¸à¸²à¸£à¸à¸¥à¸±à¹ˆà¸™à¸à¸£à¸à¸‡" +moderationNoteDescription: "คุณสามารถใส่โน้ตส่วนตัวที่เฉพาะผู้ดูà¹à¸¥à¸£à¸°à¸šà¸šà¹€à¸—่านั้นที่สามารถเข้าถึงได้" addModerationNote: "เพิ่มโน้ตà¸à¸²à¸£à¸à¸¥à¸±à¹ˆà¸™à¸à¸£à¸à¸‡" moderationLogs: "ปูมà¸à¸²à¸£à¸„วบคุมดูà¹à¸¥" nUsersMentioned: "à¸à¸¥à¹ˆà¸²à¸§à¸–ึงโดยผู้ใช้ {n} ราย" @@ -509,6 +515,10 @@ uiLanguage: "ภาษาà¸à¸´à¸™à¹€à¸—à¸à¸£à¹Œà¹€à¸Ÿà¸‹à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸‡à aboutX: "เà¸à¸µà¹ˆà¸¢à¸§à¸à¸±à¸š {x}" emojiStyle: "สไตล์ขà¸à¸‡à¹€à¸à¹‚มจิ" native: "ภาษาà¹à¸¡à¹ˆ" +menuStyle: "สไตล์เมนู" +style: "สไตล์" +drawer: "ตัววาด" +popup: "ป๊à¸à¸›à¸à¸±à¸ž" showNoteActionsOnlyHover: "à¹à¸ªà¸”งà¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¹‚น้ตเมื่à¸à¹‚ฮเวà¸à¸£à¹Œ(วางเมาส์เหนืà¸)เท่านั้น" showReactionsCount: "à¹à¸ªà¸”งจำนวนรีà¹à¸à¸à¸Šà¸±à¹ˆà¸™à¹ƒà¸™à¹‚น้ต" noHistory: "ไม่มีประวัติ" @@ -591,6 +601,8 @@ ascendingOrder: "เรียงลำดับขึ้น" descendingOrder: "เรียงลำดับลง" scratchpad: "Scratchpad" scratchpadDescription: "Scratchpad ให้สภาพà¹à¸§à¸”ล้à¸à¸¡à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£à¸—ดลà¸à¸‡ AiScript คุณสามารถเขียนโค้ด/สั่งดำเนินà¸à¸²à¸£/ตรวจสà¸à¸šà¸œà¸¥à¸¥à¸±à¸žà¸˜à¹Œ ขà¸à¸‡à¸à¸²à¸£à¹‚ต้ตà¸à¸šà¸à¸±à¸š Misskey ได้" +uiInspector: "ตัวตรวจสà¸à¸š UI" +uiInspectorDescription: "คุณสามารถตรวจสà¸à¸šà¸£à¸²à¸¢à¸Šà¸·à¹ˆà¸à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸—ี่เà¸à¸µà¹ˆà¸¢à¸§à¸‚้à¸à¸‡à¸à¸±à¸šà¸ªà¹ˆà¸§à¸™à¸›à¸£à¸°à¸à¸à¸šà¸à¸´à¸™à¹€à¸•à¸à¸£à¹Œà¹€à¸Ÿà¸‹à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ (UI) บนหน่วยความจำขà¸à¸‡à¸£à¸°à¸šà¸š ส่วนประà¸à¸à¸š UI เหล่านี้จะถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นโดยฟังà¸à¹Œà¸Šà¸±à¸™ Ui:C:" output: "เà¸à¸²à¸—์พุต" script: "สคริปต์" disablePagesScript: "ปิดà¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™ AiScript บนเพจ" @@ -909,6 +921,7 @@ followersVisibility: "à¸à¸²à¸£à¸¡à¸à¸‡à¹€à¸«à¹‡à¸™à¸œà¸¹à¹‰à¸—ี่à¸à¸³à¸¥à continueThread: "ดูความต่à¸à¹€à¸™à¸·à¹ˆà¸à¸‡à¹€à¸˜à¸£à¸”" deleteAccountConfirm: "à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸™à¸µà¹‰à¸ˆà¸°à¸¥à¸šà¸šà¸±à¸à¸Šà¸µà¸‚à¸à¸‡à¸„ุณà¸à¸¢à¹ˆà¸²à¸‡à¸–าวรเลยนะ à¹à¸™à¹ˆà¹ƒà¸ˆà¸«à¸£à¸à¸”ำเนินà¸à¸²à¸£?" incorrectPassword: "รหัสผ่านไม่ถูà¸à¸•้à¸à¸‡" +incorrectTotp: "รหัสยืนยันตัวตนà¹à¸šà¸šà¹ƒà¸Šà¹‰à¸„รั้งเดียวที่ท่านได้ระบุมานั้น ไม่ถูà¸à¸•้à¸à¸‡à¸«à¸£à¸·à¸à¸«à¸¡à¸”à¸à¸²à¸¢à¸¸à¸¥à¸‡à¹à¸¥à¹‰à¸§à¸„่ะ" voteConfirm: "ต้à¸à¸‡à¸à¸²à¸£à¹‚หวต “{choice}†ใช่ไหม?" hide: "ซ่à¸à¸™" useDrawerReactionPickerForMobile: "à¹à¸ªà¸”ง ตัวจิ้มรีà¹à¸à¸„ชั่น เป็นà¹à¸šà¸šà¸¥à¸´à¹‰à¸™à¸Šà¸±à¸ เมื่à¸à¹ƒà¸Šà¹‰à¸šà¸™à¸¡à¸·à¸à¸–ืà¸" @@ -1073,6 +1086,7 @@ retryAllQueuesConfirmTitle: "ลà¸à¸‡à¹ƒà¸«à¸¡à¹ˆà¸—ั้งหมดจริ retryAllQueuesConfirmText: "สิ่งนี้จะเพิ่มà¸à¸²à¸£à¹‚หลดเซิร์ฟเวà¸à¸£à¹Œà¸Šà¸±à¹ˆà¸§à¸„ราวนะ" enableChartsForRemoteUser: "สร้างà¹à¸œà¸™à¸ ูมิข้à¸à¸¡à¸¹à¸¥à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸£à¸°à¸¢à¸°à¹„à¸à¸¥" enableChartsForFederatedInstances: "สร้างà¹à¸œà¸™à¸ ูมิขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸£à¸°à¸¢à¸°à¹„à¸à¸¥" +enableStatsForFederatedInstances: "ดึงข้à¸à¸¡à¸¹à¸¥à¸ªà¸–ิติจาà¸à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸—ี่à¸à¸¢à¸¹à¹ˆà¸«à¹ˆà¸²à¸‡à¹„à¸à¸¥" showClipButtonInNoteFooter: "เพิ่ม “คลิป†ไปยังเมนูสั่งà¸à¸²à¸£à¸‚à¸à¸‡à¹‚น้ต" reactionsDisplaySize: "ขนาดขà¸à¸‡à¸£à¸µà¹à¸à¸„ชั่น" limitWidthOfReaction: "จำà¸à¸±à¸”ความà¸à¸§à¹‰à¸²à¸‡à¸ªà¸¹à¸‡à¸ªà¸¸à¸”ขà¸à¸‡à¸£à¸µà¹à¸à¸„ชั่นà¹à¸¥à¸°à¹à¸ªà¸”งให้เล็à¸à¸¥à¸‡" @@ -1259,6 +1273,32 @@ confirmWhenRevealingSensitiveMedia: "ตรวจสà¸à¸šà¸à¹ˆà¸à¸™à¹à¸ªà¸”à sensitiveMediaRevealConfirm: "สื่à¸à¸™à¸µà¹‰à¸¡à¸µà¹€à¸™à¸·à¹‰à¸à¸«à¸²à¸¥à¸°à¹€à¸à¸µà¸¢à¸”à¸à¹ˆà¸à¸™, ต้à¸à¸‡à¸à¸²à¸£à¹à¸ªà¸”งใช่ไหม?" createdLists: "รายชื่à¸à¸—ี่ถูà¸à¸ªà¸£à¹‰à¸²à¸‡" createdAntennas: "เสาà¸à¸²à¸à¸²à¸¨à¸—ี่ถูà¸à¸ªà¸£à¹‰à¸²à¸‡" +fromX: "จาภ{x}" +genEmbedCode: "สร้างรหัสà¸à¸±à¸‡" +noteOfThisUser: "โน้ตโดยผู้ใช้นี้" +clipNoteLimitExceeded: "ไม่สามารถเพิ่มโน้ตเพิ่มเติมในคลิปนี้ได้à¸à¸µà¸à¹à¸¥à¹‰à¸§" +performance: "ประสิทธิภาพ​" +modified: "à¹à¸à¹‰à¹„ข" +discard: "ละทิ้ง" +thereAreNChanges: "มีà¸à¸¢à¸¹à¹ˆ {n} เปลี่ยนà¹à¸›à¸¥à¸‡(s)" +signinWithPasskey: "ลงชื่à¸à¹€à¸‚้าใช้ด้วย Passkey" +unknownWebAuthnKey: "พาสคีย์ไม่ถูà¸à¸•้à¸à¸‡à¸„่ะ" +passkeyVerificationFailed: "à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸à¸¸à¸à¹à¸ˆà¸”ิจิทัลไม่สำเร็จค่ะ" +passkeyVerificationSucceededButPasswordlessLoginDisabled: "à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸žà¸²à¸ªà¸„ีย์สำเร็จà¹à¸¥à¹‰à¸§ à¹à¸•่à¸à¸²à¸£à¸¥à¸‡à¸Šà¸·à¹ˆà¸à¹€à¸‚้าใช้à¹à¸šà¸šà¹„ม่ต้à¸à¸‡à¹ƒà¸ªà¹ˆà¸£à¸«à¸±à¸ªà¸œà¹ˆà¸²à¸™à¸–ูà¸à¸›à¸´à¸”ใช้งานà¹à¸¥à¹‰à¸§" +messageToFollower: "ข้à¸à¸„วามถึงผู้ติดตาม" +target: "เป้า" +testCaptchaWarning: "ฟังà¸à¹Œà¸Šà¸±à¸™à¸™à¸µà¹‰à¸¡à¸µà¹„ว้สำหรับทดสà¸à¸š CAPTCHA เท่านั้น\n<strong>ห้ามนำไปใช้ในระบบจริงโดยเด็ดขาด</strong>" +prohibitedWordsForNameOfUser: "คำนี้ไม่สามารถใช้เป็นชื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¹„ด้" +prohibitedWordsForNameOfUserDescription: "หาà¸à¸¡à¸µà¸ªà¸•ริงใดๆ ในรายà¸à¸²à¸£à¸™à¸µà¹‰à¸›à¸£à¸²à¸à¸à¸à¸¢à¸¹à¹ˆà¹ƒà¸™à¸Šà¸·à¹ˆà¸à¸‚à¸à¸‡à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ ชื่à¸à¸™à¸±à¹‰à¸™à¸ˆà¸°à¸–ูà¸à¸›à¸à¸´à¹€à¸ªà¸˜ ผู้ใช้ที่มีสิทธิ์à¹à¸•่ผู้ดูà¹à¸¥à¸£à¸°à¸šà¸šà¸™à¸±à¹‰à¸™à¸ˆà¸°à¹„ม่ได้รับผลà¸à¸£à¸°à¸—บใดๆจาà¸à¸‚้à¸à¸ˆà¸³à¸à¸±à¸”นี้ค่ะ" +yourNameContainsProhibitedWords: "ชื่à¸à¸‚à¸à¸‡à¸„ุณนั้นมีคำที่ต้à¸à¸‡à¸«à¹‰à¸²à¸¡" +yourNameContainsProhibitedWordsDescription: "ถ้าหาà¸à¸„ุณต้à¸à¸‡à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸Šà¸·à¹ˆà¸à¸™à¸µà¹‰ à¸à¸£à¸¸à¸“าติดต่à¸à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¸£à¸°à¸šà¸šà¸‚à¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸™à¸°à¸„่ะ" +_abuseUserReport: + forward: "ส่ง​ต่à¸" + forwardDescription: "ส่งรายงานไปยังเซิร์ฟเวà¸à¸£à¹Œà¸£à¸°à¸¢à¸°à¹„à¸à¸¥à¹‚ดยใช้บัà¸à¸Šà¸µà¸£à¸°à¸šà¸šà¸—ี่ไม่ระบุตัวตน" + resolve: "à¹à¸à¹‰à¹„ข" + accept: "ยà¸à¸¡à¸£à¸±à¸š" + reject: "ปà¸à¸´à¹€à¸ªà¸˜" + resolveTutorial: "ถ้าหาà¸à¸£à¸²à¸¢à¸‡à¸²à¸™à¸™à¸µà¹‰à¸¡à¸µà¹€à¸™à¸·à¹‰à¸à¸«à¸²à¸–ูà¸à¸•้à¸à¸‡ ให้เลืà¸à¸ \"ยà¸à¸¡à¸£à¸±à¸š\" เพื่à¸à¸›à¸´à¸”เคสà¸à¸£à¸“ีนี้โดยถืà¸à¸§à¹ˆà¸²à¹„ด้รับà¸à¸²à¸£à¹à¸à¹‰à¹„ขà¹à¸¥à¹‰à¸§\nถ้าหาà¸à¹€à¸™à¸·à¹‰à¸à¸«à¸²à¹ƒà¸™à¸£à¸²à¸¢à¸‡à¸²à¸™à¸™à¸µà¹‰à¸™à¸±à¹‰à¸™à¹„ม่ถูà¸à¸•้à¸à¸‡ ให้เลืà¸à¸ \"ปà¸à¸´à¹€à¸ªà¸˜\" เพื่à¸à¸›à¸´à¸”เคสà¸à¸£à¸“ีนี้โดยถืà¸à¸§à¹ˆà¸²à¹„ม่ได้รับà¸à¸²à¸£à¹à¸à¹‰à¹„ข" _delivery: status: "สถานะà¸à¸²à¸£à¸ˆà¸±à¸”ส่ง" stop: "ระงับà¸à¸²à¸£à¸ªà¹ˆà¸‡" @@ -1393,8 +1433,10 @@ _serverSettings: fanoutTimelineDescription: "เพิ่มประสิทธิภาพà¸à¸²à¸£à¸”ึงข้à¸à¸¡à¸¹à¸¥à¹„ทม์ไลน์à¸à¸¢à¹ˆà¸²à¸‡à¸¡à¸²à¸ à¹à¸¥à¸°à¸¥à¸”ภาระในà¸à¸²à¸™à¸‚้à¸à¸¡à¸¹à¸¥à¹€à¸¡à¸·à¹ˆà¸à¹€à¸›à¸´à¸”ใช้งาน ในทางà¸à¸¥à¸±à¸šà¸à¸±à¸™ à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸«à¸™à¹ˆà¸§à¸¢à¸„วามจำขà¸à¸‡ Redis จะเพิ่มขึ้น ลà¸à¸‡à¸›à¸´à¸”à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¸™à¸µà¹‰à¹ƒà¸™à¸à¸£à¸“ีที่หน่วยความจำเซิร์ฟเวà¸à¸£à¹Œà¹€à¸«à¸¥à¸·à¸à¸™à¹‰à¸à¸¢à¸«à¸£à¸·à¸à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¹„ม่เสถียร" fanoutTimelineDbFallback: "ฟà¸à¸¥à¹à¸šà¹Šà¸à¸à¸¥à¸±à¸šà¸à¸²à¸™à¸‚้à¸à¸¡à¸¹à¸¥" fanoutTimelineDbFallbackDescription: "เมื่à¸à¹€à¸›à¸´à¸”ใช้งาน หาà¸à¹„ม่ได้à¹à¸„ชไทม์ไลน์ ไทม์ไลน์จะฟà¸à¸¥à¹à¸šà¹Šà¸à¹„ปยังà¸à¸²à¸™à¸‚้à¸à¸¡à¸¹à¸¥à¸ªà¸³à¸«à¸£à¸±à¸šà¸à¸²à¸£ query เพิ่มเติม à¸à¸²à¸£à¸›à¸´à¸”ใช้งานจะช่วยลดภาระขà¸à¸‡à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¸”้วยà¸à¸²à¸£à¸à¸³à¸ˆà¸±à¸”à¸à¸£à¸°à¸šà¸§à¸™à¸Ÿà¸à¸¥à¹à¸šà¹Šà¸ à¹à¸•่มันà¸à¹‡à¸ˆà¸°à¸ˆà¸³à¸à¸±à¸”ช่วงเวลาไทม์ไลน์ที่สามารถดึงข้à¸à¸¡à¸¹à¸¥à¹„ด้" + reactionsBufferingDescription: "เมื่à¸à¹€à¸›à¸´à¸”ใช้งานฟังà¸à¹Œà¸Šà¸±à¸™à¸™à¸µà¹‰à¸à¹‡à¸ˆà¸°à¸Šà¹ˆà¸§à¸¢à¸¥à¸” latency ในà¸à¸²à¸£à¸ªà¸£à¹‰à¸²à¸‡à¸›à¸à¸´à¸à¸´à¸£à¸´à¸¢à¸² à¹à¸•่à¸à¸²à¸ˆà¸ˆà¸°à¸ªà¹ˆà¸‡à¸œà¸¥à¹ƒà¸«à¹‰ memory footprint ขà¸à¸‡ Redis เพิ่มขึ้นนะ" inquiryUrl: "URL สำหรับà¸à¸²à¸£à¸•ิดต่à¸à¸ªà¸à¸šà¸–าม" inquiryUrlDescription: "ระบุ URL ขà¸à¸‡à¸«à¸™à¹‰à¸²à¹€à¸§à¹‡à¸šà¸—ี่มีà¹à¸šà¸šà¸Ÿà¸à¸£à¹Œà¸¡à¸ªà¸³à¸«à¸£à¸±à¸šà¸•ิดต่à¸à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œ หรืà¸à¸‚้à¸à¸¡à¸¹à¸¥à¸à¸²à¸£à¸•ิดต่à¸à¸‚à¸à¸‡à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œ" + thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ถ้าหาà¸à¹„ม่มีà¸à¸²à¸£à¸•รวจสà¸à¸šà¸ˆà¸²à¸à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¸£à¸°à¸šà¸šà¸«à¸£à¸·à¸à¹„ม่มีความเคลื่à¸à¸™à¹„หวมาเป็นระยะเวลาหนึ่ง ระบบจะทำà¸à¸²à¸£à¸›à¸´à¸”ใช้งานฟังà¸à¹Œà¸Šà¸±à¸™à¸™à¸µà¹‰à¹‚ดยà¸à¸±à¸•โนมัติ เพื่à¸à¸¥à¸”ความเสี่ยงในà¸à¸²à¸£à¸–ูà¸à¹‚จมตีด้วยสà¹à¸›à¸¡à¹à¸¥à¸°à¸à¸·à¹ˆà¸™à¹†" _accountMigration: moveFrom: "ย้ายจาà¸à¸šà¸±à¸à¸Šà¸µà¸à¸·à¹ˆà¸™à¸¡à¸²à¸—ี่บัà¸à¸Šà¸µà¸™à¸µà¹‰" moveFromSub: "สร้างนามà¹à¸à¸‡à¹„ปยังบัà¸à¸Šà¸µà¸à¸·à¹ˆà¸™" @@ -1726,6 +1768,11 @@ _role: canSearchNotes: "à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸à¸²à¸£à¸„้นหาโน้ต" canUseTranslator: "à¸à¸²à¸£à¹ƒà¸Šà¹‰à¸‡à¸²à¸™à¹à¸›à¸¥" avatarDecorationLimit: "จำนวนà¸à¸²à¸£à¸•à¸à¹à¸•่งไà¸à¸„à¸à¸™à¸ªà¸¹à¸‡à¸ªà¸¸à¸”ที่สามารถติดตั้งได้" + canImportAntennas: "à¸à¸™à¸¸à¸à¸²à¸•ให้นำเข้าเสาà¸à¸²à¸à¸²à¸¨" + canImportBlocking: "à¸à¸™à¸¸à¸à¸²à¸•ให้นำเข้าà¸à¸²à¸£à¸šà¸¥à¹‡à¸à¸" + canImportFollowing: "à¸à¸™à¸¸à¸à¸²à¸•ให้นำเข้ารายà¸à¸²à¸£à¸•่à¸à¹„ปนี้" + canImportMuting: "à¸à¸™à¸¸à¸à¸²à¸•ให้นำเข้าà¸à¸²à¸£à¸›à¸´à¸”à¸à¸±à¹‰à¸™" + canImportUserLists: "à¸à¸™à¸¸à¸à¸²à¸•ให้นำเข้ารายà¸à¸²à¸£" _condition: roleAssignedTo: "มà¸à¸šà¸«à¸¡à¸²à¸¢à¹ƒà¸«à¹‰à¸¡à¸µà¸šà¸—บาทà¹à¸šà¸šà¸—ำมืà¸" isLocal: "ผู้ใช้ท้à¸à¸‡à¸–ิ่น" @@ -2219,6 +2266,9 @@ _profile: changeBanner: "เปลี่ยนà¹à¸šà¸™à¹€à¸™à¸à¸£à¹Œ" verifiedLinkDescription: "หาà¸à¸›à¹‰à¸à¸™ URL ที่มีลิงà¸à¹Œà¹„ปยังโปรไฟล์ขà¸à¸‡à¸„ุณ ไà¸à¸„à¸à¸™à¸à¸²à¸£à¸¢à¸·à¸™à¸¢à¸±à¸™à¸„วามเป็นเจ้าขà¸à¸‡à¸ˆà¸°à¹à¸ªà¸”งถัดจาà¸à¸Ÿà¸´à¸¥à¸”์นั้น ๆ" avatarDecorationMax: "คุณสามารถเพิ่มà¸à¸²à¸£à¸•à¸à¹à¸•่งได้สูงสุด {max}" + followedMessage: "ส่งข้à¸à¸„วามเมื่à¸à¸¡à¸µà¸„นà¸à¸”ติดตาม" + followedMessageDescription: "ส่งข้à¸à¸„วามเมื่à¸à¸¡à¸µà¸„นà¸à¸”ติดตามà¹à¸¥à¹‰à¸§" + followedMessageDescriptionForLockedAccount: "ถ้าหาà¸à¸„ุณตั้งค่าให้คนà¸à¸·à¹ˆà¸™à¸•้à¸à¸‡à¸‚à¸à¸à¸™à¸¸à¸à¸²à¸•à¸à¹ˆà¸à¸™à¸—ี่จะติดตามคุณ ระบบจะขึ้นข้à¸à¸„วามนี้ในตà¸à¸™à¸—ี่คุณà¸à¸™à¸¸à¸¡à¸±à¸•ิให้เขาติดตาม" _exportOrImport: allNotes: "โน้ตทั้งหมด" favoritedNotes: "โน้ตที่ถูà¸à¹ƒà¸ˆà¹„ว้" @@ -2311,6 +2361,7 @@ _pages: eyeCatchingImageSet: "ตั้งค่าภาพขนาดย่à¸" eyeCatchingImageRemove: "ลบภาพขนาดย่à¸" chooseBlock: "เพิ่มบล็à¸à¸„" + enterSectionTitle: "ป้à¸à¸™à¸Šà¸·à¹ˆà¸à¸«à¸±à¸§à¸‚้à¸" selectType: "เลืà¸à¸à¸Šà¸™à¸´à¸”" contentBlocks: "เนื้à¸à¸«à¸²" inputBlocks: "ป้à¸à¸™à¸‚้à¸à¸¡à¸¹à¸¥" @@ -2356,6 +2407,8 @@ _notification: renotedBySomeUsers: "รีโน้ตจาà¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰ {n} ราย" followedBySomeUsers: "มีผู้ติดตาม {n} ราย" flushNotification: "ล้างประวัติà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™" + exportOfXCompleted: "à¸à¸²à¸£à¸”ำเนินà¸à¸²à¸£à¸ªà¹ˆà¸‡à¸à¸à¸ {x} ได้เสร็จสิ้นลงà¹à¸¥à¹‰à¸§" + login: "มีคนล็à¸à¸à¸à¸´à¸™" _types: all: "ทั้งหมด" note: "โน้ตใหม่" @@ -2370,7 +2423,9 @@ _notification: followRequestAccepted: "à¸à¸™à¸¸à¸¡à¸±à¸•ิให้ติดตามà¹à¸¥à¹‰à¸§" roleAssigned: "ให้บทบาท" achievementEarned: "ปลดล็à¸à¸à¸„วามสำเร็จà¹à¸¥à¹‰à¸§" + exportCompleted: "à¸à¸£à¸°à¸šà¸§à¸™à¸à¸²à¸£à¸ªà¹ˆà¸‡à¸à¸à¸à¸‚้à¸à¸¡à¸¹à¸¥à¹„ด้เสร็จสิ้นสมบูรณ์à¹à¸¥à¹‰à¸§" login: "เข้าสู่ระบบ" + test: "ทดสà¸à¸šà¸£à¸°à¸šà¸šà¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™" app: "à¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸ˆà¸²à¸à¹à¸à¸›à¸—ี่มีลิงà¸à¹Œ" _actions: followBack: "ติดตามà¸à¸¥à¸±à¸šà¸”้วย" @@ -2436,7 +2491,10 @@ _webhookSettings: abuseReport: "เมื่à¸à¸¡à¸µà¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™à¸ˆà¸²à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰" abuseReportResolved: "เมื่à¸à¸¡à¸µà¸à¸²à¸£à¸ˆà¸±à¸”à¸à¸²à¸£à¸à¸±à¸šà¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™à¸ˆà¸²à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰" userCreated: "เมื่à¸à¸œà¸¹à¹‰à¹ƒà¸Šà¹‰à¸–ูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้น" + inactiveModeratorsWarning: "เมื่à¸à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¸£à¸°à¸šà¸šà¹„ม่ได้ใช้งานมานานระยะหนึ่ง" + inactiveModeratorsInvitationOnlyChanged: "เมื่à¸à¸œà¸¹à¹‰à¸”ูà¹à¸¥à¸£à¸°à¸šà¸šà¸—ี่ไม่ได้ใช้งานมานาน à¹à¸¥à¸°à¹€à¸‹à¸´à¸£à¹Œà¸Ÿà¹€à¸§à¸à¸£à¹Œà¹€à¸›à¸¥à¸µà¹ˆà¸¢à¸™à¹€à¸›à¹‡à¸™à¹à¸šà¸šà¹€à¸Šà¸´à¸à¹€à¸‚้าร่วมเท่านั้น" deleteConfirm: "ต้à¸à¸‡à¸à¸²à¸£à¸¥à¸š Webhook ใช่ไหม?" + testRemarks: "คลิà¸à¸›à¸¸à¹ˆà¸¡à¸—างด้านขวาขà¸à¸‡à¸ªà¸§à¸´à¸•ช์เพื่à¸à¸ªà¹ˆà¸‡ Webhook ทดสà¸à¸šà¸—ี่มีข้à¸à¸¡à¸¹à¸¥à¸ˆà¸³à¸¥à¸à¸‡" _abuseReport: _notificationRecipient: createRecipient: "เพิ่มปลายทางà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™" @@ -2480,6 +2538,8 @@ _moderationLogTypes: markSensitiveDriveFile: "ทำเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¹„ฟล์ว่ามีเนื้à¸à¸«à¸²à¸¥à¸°à¹€à¸à¸µà¸¢à¸”à¸à¹ˆà¸à¸™" unmarkSensitiveDriveFile: "ยà¸à¹€à¸¥à¸´à¸à¸—ำเครื่à¸à¸‡à¸«à¸¡à¸²à¸¢à¹„ฟล์ว่ามีเนื้à¸à¸«à¸²à¸¥à¸°à¹€à¸à¸µà¸¢à¸”à¸à¹ˆà¸à¸™" resolveAbuseReport: "รายงานได้รับà¸à¸²à¸£à¹à¸à¹‰à¹„ขà¹à¸¥à¹‰à¸§" + forwardAbuseReport: "ได้ส่งรายงานไปà¹à¸¥à¹‰à¸§" + updateAbuseReportNote: "โน้ตà¸à¸²à¸£à¸à¸¥à¸±à¹ˆà¸™à¸à¸£à¸à¸‡à¸—ี่รายงานไปนั้น ได้รับà¸à¸²à¸£à¸à¸±à¸›à¹€à¸”ตà¹à¸¥à¹‰à¸§" createInvitation: "สร้างรหัสเชิà¸" createAd: "สร้างโฆษณาà¹à¸¥à¹‰à¸§" deleteAd: "ลบโฆษณาà¸à¸à¸à¹à¸¥à¹‰à¸§" @@ -2495,6 +2555,10 @@ _moderationLogTypes: createAbuseReportNotificationRecipient: "สร้างปลายทางà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™" updateAbuseReportNotificationRecipient: "à¸à¸±à¸›à¹€à¸”ตปลายทางà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™" deleteAbuseReportNotificationRecipient: "ลบปลายทางà¸à¸²à¸£à¹à¸ˆà¹‰à¸‡à¹€à¸•ืà¸à¸™à¸à¸²à¸£à¸£à¸²à¸¢à¸‡à¸²à¸™" + deleteAccount: "บัà¸à¸Šà¸µà¸–ูà¸à¸¥à¸šà¹„ปà¹à¸¥à¹‰à¸§" + deletePage: "เพจถูà¸à¸¥à¸šà¸à¸à¸à¹„ปà¹à¸¥à¹‰à¸§" + deleteFlash: "Play ถูà¸à¸¥à¸šà¸à¸à¸à¹„ปà¹à¸¥à¹‰à¸§" + deleteGalleryPost: "โพสต์à¹à¸à¸¥à¹€à¸¥à¸à¸£à¸µà¹ˆà¸–ูà¸à¸¥à¸šà¸à¸à¸à¹à¸¥à¹‰à¸§" _fileViewer: title: "รายละเà¸à¸µà¸¢à¸”ไฟล์" type: "ประเภทไฟล์" @@ -2631,3 +2695,17 @@ _contextMenu: app: "à¹à¸à¸›à¸žà¸¥à¸´à¹€à¸„ชัน" appWithShift: "à¹à¸à¸›à¸Ÿà¸¥à¸´à¹€à¸„ชันด้วยปุ่มยà¸à¹à¸„ร่ (Shift)" native: "UI ขà¸à¸‡à¹€à¸šà¸£à¸²à¸§à¹Œà¹€à¸‹à¸à¸£à¹Œ" +_embedCodeGen: + title: "ปรับà¹à¸•่งโค้ดà¸à¸±à¸‡" + header: "à¹à¸ªà¸”งส่วนหัว" + autoload: "โหลดเพิ่มโดยà¸à¸±à¸•โนมัติ (เลิà¸à¹ƒà¸Šà¹‰à¹à¸¥à¹‰à¸§)" + maxHeight: "ความสูงสุด" + maxHeightDescription: "หาà¸à¸–้าตั้งค่าเป็น 0 จะทำให้ไม่มีà¸à¸²à¸£à¸ˆà¸³à¸à¸±à¸”ความสูงขà¸à¸‡à¸§à¸´à¸”เจ็ต à¹à¸•่ควรตั้งค่าเป็นตัวเลขà¸à¸·à¹ˆà¸™à¹† เพื่à¸à¹„ม่ให้วิดเจ็ตยืดตัวลงไปเรื่à¸à¸¢à¹†" + maxHeightWarn: "à¸à¸²à¸£à¸ˆà¸³à¸à¸±à¸”ความสูงสูงสุดถูà¸à¸›à¸´à¸”ใช้งาน (0) หาà¸à¹„ม่ได้ตั้งใจให้เป็นเช่นนี้ โปรดตั้งค่าความสูงสูงสุดให้เป็นค่าà¸à¸·à¹ˆà¸™à¹†à¹à¸—น" + previewIsNotActual: "à¸à¸²à¸£à¹à¸ªà¸”งผลนั้นต่างจาà¸à¸à¸²à¸£à¸à¸±à¸‡à¸ˆà¸£à¸´à¸‡à¹€à¸žà¸£à¸²à¸°à¹€à¸à¸´à¸™à¸‚à¸à¸šà¹€à¸‚ตที่à¹à¸ªà¸”งบนหน้าจà¸à¸•ัวà¸à¸¢à¹ˆà¸²à¸‡à¸™à¸°" + rounded: "ทำให้มันà¸à¸¥à¸¡" + border: "เพิ่มขà¸à¸šà¹ƒà¸«à¹‰à¸à¸±à¸šà¸à¸£à¸à¸šà¸”้านนà¸à¸" + applyToPreview: "นำไปใช้à¸à¸±à¸šà¸à¸²à¸£à¹à¸ªà¸”งตัวà¸à¸¢à¹ˆà¸²à¸‡" + generateCode: "สร้างโค้ดสำหรับà¸à¸²à¸£à¸à¸±à¸‡" + codeGenerated: "รหัสถูà¸à¸ªà¸£à¹‰à¸²à¸‡à¸‚ึ้นà¹à¸¥à¹‰à¸§" + codeGeneratedDescription: "นำโค้ดที่สร้างà¹à¸¥à¹‰à¸§à¹„ปวางในเว็บไซต์ขà¸à¸‡à¸„ุณเพื่à¸à¸à¸±à¸‡à¹€à¸™à¸·à¹‰à¸à¸«à¸²" diff --git a/locales/tr-TR.yml b/locales/tr-TR.yml index fe2f158ff6..69892fedc8 100644 --- a/locales/tr-TR.yml +++ b/locales/tr-TR.yml @@ -344,7 +344,6 @@ today: "Bugün" monthX: "{month} ay" pages: "Sayfalar" integration: "Entegrasyon" -enableRegistration: "Kayıtlara izin ver" basicInfo: "Temel bilgiler" pinnedUsers: "SabitlenmiÅŸ kullanıcılar" pinnedNotes: "Sabitlenen" diff --git a/locales/uk-UA.yml b/locales/uk-UA.yml index f2262cd71f..1b21854650 100644 --- a/locales/uk-UA.yml +++ b/locales/uk-UA.yml @@ -334,7 +334,6 @@ enableLocalTimeline: "Увімкнути локальну Ñтрічку" enableGlobalTimeline: "Увімкнути глобальну Ñтрічку" disablingTimelinesInfo: "ÐдмініÑтратори та модератори завжди мають доÑтуп до вÑÑ–Ñ… Ñтрічок, навіть Ñкщо вони вимкнуті." registration: "РеєÑтраціÑ" -enableRegistration: "Дозволити реєÑтрацію" invite: "ЗапроÑити" driveCapacityPerLocalAccount: "Об'єм диÑка на одного локального кориÑтувача" driveCapacityPerRemoteAccount: "Об'єм диÑка на одного віддаленого кориÑтувача" diff --git a/locales/uz-UZ.yml b/locales/uz-UZ.yml index 37a550008a..051a4ae6c5 100644 --- a/locales/uz-UZ.yml +++ b/locales/uz-UZ.yml @@ -349,7 +349,6 @@ enableLocalTimeline: "Mahalliy vaqt mintaqasini yoqing" enableGlobalTimeline: "Global vaqt mintaqasini yoqing" disablingTimelinesInfo: "Administratorlar va Moderatorlar har doim barcha vaqt jadvallariga kirish huquqiga ega bo'ladilar, hatto ular yoqilmagan bo'lsa ham." registration: "Ro'yxatdan o'tish" -enableRegistration: "Ro'yxatdan o'tishni yoqing" invite: "Taklif qilish" driveCapacityPerLocalAccount: "Har bir mahalliy foydalanuvchi uchun disk maydoni" driveCapacityPerRemoteAccount: "Har bir masofaviy foydalanuvchi uchun disk maydoni" diff --git a/locales/vi-VN.yml b/locales/vi-VN.yml index 235497d844..24faa4b94c 100644 --- a/locales/vi-VN.yml +++ b/locales/vi-VN.yml @@ -8,6 +8,9 @@ search: "Tìm kiếm" notifications: "Thông báo" username: "Tên ngưá»i dùng" password: "Máºt khẩu" +initialPasswordForSetup: "Máºt khẩu ban đầu để thiết láºp" +initialPasswordIsIncorrect: "Máºt khẩu ban đầu đã nháºp sai" +initialPasswordForSetupDescription: "Nếu bạn tá»± cà i đặt Misskey, hãy sá» dụng máºt khẩu ban đầu cá»§a bạn đã nháºp trong tệp cấu hình.\nNếu bạn Ä‘ang sá» dụng dịch vụ nà o đó giống như dịch vụ lưu trữ cá»§a Misskey, hãy sá» dụng máºt khẩu ban đầu được cung cấp.\nNếu bạn chưa đặt máºt khẩu ban đầu, vui lòng để trống và tiếp tục." forgotPassword: "Quên máºt khẩu" fetchingAsApObject: "Äang nạp dữ liệu từ Fediverse..." ok: "Äồng ý" @@ -354,7 +357,6 @@ enableLocalTimeline: "Báºt bảng tin máy chá»§" enableGlobalTimeline: "Báºt bảng tin liên hợp" disablingTimelinesInfo: "Quản trị viên và Kiểm duyệt viên luôn có quyá»n truy cáºp má»i bảng tin, kể cả khi chúng không được báºt." registration: "Äăng ký" -enableRegistration: "Cho phép đăng ký má»›i" invite: "Má»i" driveCapacityPerLocalAccount: "Dung lượng ổ đĩa tối Ä‘a cho má»—i ngưá»i dùng" driveCapacityPerRemoteAccount: "Dung lượng ổ đĩa tối Ä‘a cho má»—i ngưá»i dùng từ xa" diff --git a/locales/zh-CN.yml b/locales/zh-CN.yml index b81018cc1f..e6232070d7 100644 --- a/locales/zh-CN.yml +++ b/locales/zh-CN.yml @@ -107,7 +107,7 @@ follow: "关注" followRequest: "关注申请" followRequests: "关注申请" unfollow: "å–æ¶ˆå…³æ³¨" -followRequestPending: "关注请求批准ä¸" +followRequestPending: "关注请求待批准" enterEmoji: "输入表情符å·" renote: "转å‘" unrenote: "å–æ¶ˆè½¬å‘" @@ -136,15 +136,15 @@ overwriteFromPinnedEmojisForReaction: "从「置顶(回应)ã€è®¾ç½®è¦†ç›–" overwriteFromPinnedEmojis: "从全局设置覆盖" reactionSettingDescription2: "æ‹–åŠ¨é‡æ–°æŽ’åºï¼Œå•å‡»åˆ é™¤ï¼Œç‚¹å‡» + æ·»åŠ ã€‚" rememberNoteVisibility: "ä¿å˜ä¸Šæ¬¡è®¾ç½®çš„å¯è§æ€§" -attachCancel: "åˆ é™¤é™„ä»¶" +attachCancel: "å–æ¶ˆæ·»åР附件" deleteFile: "åˆ é™¤æ–‡ä»¶" markAsSensitive: "æ ‡è®°ä¸ºæ•æ„Ÿå†…容" unmarkAsSensitive: "å–æ¶ˆæ ‡è®°ä¸ºæ•感内容" enterFileName: "输入文件å" mute: "å±è”½" unmute: "解除é™éŸ³" -renoteMute: "å±è”½è½¬å¸–" -renoteUnmute: "解除å±è”½è½¬å¸–" +renoteMute: "éšè—转帖" +renoteUnmute: "解除éšè—转帖" block: "拉黑" unblock: "å–æ¶ˆæ‹‰é»‘" suspend: "冻结" @@ -213,8 +213,8 @@ charts: "图表" perHour: "æ¯å°æ—¶" perDay: "æ¯å¤©" stopActivityDelivery: "åœæ¢å‘逿´»åЍ" -blockThisInstance: "é˜»æ¢æ¤æœåС噍呿œ¬æœåŠ¡å™¨æŽ¨æµ" -silenceThisInstance: "使æœåС噍é™éŸ³" +blockThisInstance: "å±è”½æ¤æœåС噍" +silenceThisInstance: "é™éŸ³æ¤æœåС噍" mediaSilenceThisInstance: "éšè—æ¤æœåŠ¡å™¨çš„åª’ä½“æ–‡ä»¶" operations: "æ“作" software: "软件" @@ -233,17 +233,17 @@ clearQueueConfirmTitle: "确定清除队列?" clearQueueConfirmText: "未é€è¾¾çš„帖åå°†ä¸ä¼šè¢«æŠ•递。 é€šå¸¸æ— éœ€æ‰§è¡Œæ¤æ“作。" clearCachedFiles: "清除缓å˜" clearCachedFilesConfirm: "ç¡®å®šè¦æ¸…除所有缓å˜çš„远程文件?" -blockedInstances: "被å°é”çš„æœåС噍" -blockedInstancesDescription: "设定è¦å°é”çš„æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。被å°é”çš„æœåŠ¡å™¨å°†æ— æ³•ä¸Žæœ¬æœåŠ¡å™¨è¿›è¡Œäº¤æ¢é€šè®¯ã€‚å域åä¹ŸåŒæ ·ä¼šè¢«å°é”。" +blockedInstances: "被å±è”½çš„æœåŠ¡å™¨" +blockedInstancesDescription: "设定è¦å±è”½çš„æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。被å±è”½çš„æœåŠ¡å™¨å°†æ— æ³•ä¸Žæœ¬æœåŠ¡å™¨è¿›è¡Œäº¤æ¢é€šè®¯ã€‚å域åä¹ŸåŒæ ·ä¼šè¢«å±è”½ã€‚" silencedInstances: "被é™éŸ³çš„æœåŠ¡å™¨" silencedInstancesDescription: "设置è¦é™éŸ³çš„æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。被é™éŸ³çš„æœåŠ¡å™¨å†…æ‰€æœ‰çš„è´¦æˆ·å°†é»˜è®¤å¤„äºŽã€Œé™éŸ³ã€çжæ€ï¼Œä»…能å‘é€å…³æ³¨è¯·æ±‚,并且在未关注状æ€ä¸‹æ— 法æåŠæœ¬åœ°è´¦æˆ·ã€‚被阻æ¢çš„实例ä¸å—å½±å“。" mediaSilencedInstances: "å·²éšè—媒体文件的æœåС噍" mediaSilencedInstancesDescription: "设置è¦éšè—媒体文件的æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。被设置为éšè—媒体文件æœåŠ¡å™¨å†…æ‰€æœ‰è´¦å·çš„æ–‡ä»¶å‡æŒ‰ç…§ã€Œæ•感内容ã€å¤„ç†ï¼Œä¸”å°†æ— æ³•ä½¿ç”¨è‡ªå®šä¹‰è¡¨æƒ…ç¬¦å·ã€‚被阻æ¢çš„实例ä¸å—å½±å“。" federationAllowedHosts: "å…许è”åˆçš„æœåŠ¡å™¨" federationAllowedHostsDescription: "设定å…许è”åˆçš„æœåŠ¡å™¨ï¼Œä»¥æ¢è¡Œåˆ†éš”。" -muteAndBlock: "é™éŸ³/拉黑" -mutedUsers: "å·²é™éŸ³ç”¨æˆ·" -blockedUsers: "已拉黑的用户" +muteAndBlock: "éšè—å’Œå±è”½" +mutedUsers: "å·²éšè—用户" +blockedUsers: "å·²å±è”½çš„用户" noUsers: "æ— ç”¨æˆ·" editProfile: "编辑资料" noteDeleteConfirm: "è¦åˆ 除该帖åå—?" @@ -258,7 +258,7 @@ noCustomEmojis: "没有自定义表情符å·" noJobs: "没有任务" federating: "è”åˆä¸" blocked: "已拉黑" -suspended: "åœæ¢æŽ¨æµ" +suspended: "åœæ¢æŠ•递" all: "全部" subscribing: "已订阅" publishing: "投递ä¸" @@ -382,7 +382,6 @@ enableLocalTimeline: "å¯ç”¨æœ¬åœ°æ—¶é—´çº¿" enableGlobalTimeline: "å¯ç”¨å…¨å±€æ—¶é—´çº¿" disablingTimelinesInfo: "å³ä½¿æ—¶é—´çº¿åŠŸèƒ½è¢«ç¦ç”¨ï¼Œå‡ºäºŽæ–¹ä¾¿ï¼Œç®¡ç†å‘˜å’Œç›‘察员也å¯ä»¥ç»§ç»ä½¿ç”¨ã€‚" registration: "注册" -enableRegistration: "å…许任何人注册" invite: "邀请" driveCapacityPerLocalAccount: "æ¯ä¸ªç”¨æˆ·çš„网盘容é‡" driveCapacityPerRemoteAccount: "æ¯ä¸ªè¿œç¨‹ç”¨æˆ·çš„网盘容é‡" @@ -587,6 +586,7 @@ masterVolume: "主音é‡" notUseSound: "é™éŸ³" useSoundOnlyWhenActive: "仅在 Misskey 活跃时输出声音" details: "详情" +renoteDetails: "转帖详情" chooseEmoji: "选择表情符å·" unableToProcess: "æ“ä½œæ— æ³•å®Œæˆ" recentUsed: "最近使用" @@ -603,7 +603,7 @@ descendingOrder: "é™åº" scratchpad: "AiScript 控制å°" scratchpadDescription: "AiScript 控制å°ä¸º AiScript æä¾›äº†å®žéªŒçŽ¯å¢ƒã€‚æ‚¨å¯ä»¥ç¼–写代ç 与 Misskey 交互,è¿è¡Œå¹¶æŸ¥çœ‹ç»“果。" uiInspector: "UI 检查器" -uiInspectorDescription: "查看所有内å˜ä¸ç”± UI 组件生æˆå‡ºçš„实例。UI 组件由 UI:C 系列函数所生æˆã€‚" +uiInspectorDescription: "查看内å˜ä¸æ‰€æœ‰ç”± UI 组件生æˆå‡ºçš„实例。UI 组件由 UI:C 系列函数所生æˆã€‚" output: "输出" script: "脚本" disablePagesScript: "ç¦ç”¨é¡µé¢è„šæœ¬" @@ -683,11 +683,11 @@ emptyToDisableSmtpAuth: "用户å和密ç 留空å¯ä»¥ç¦ç”¨ SMTP 验è¯" smtpSecure: "在 SMTP 连接ä¸ä½¿ç”¨éšå¼ SSL / TLS" smtpSecureInfo: "使用 STARTTLS æ—¶å…³é—。" testEmail: "邮件å‘逿µ‹è¯•" -wordMute: "æ–‡å—å±è”½" +wordMute: "éšè—æ–‡å—" hardWordMute: "å±è”½å…³é”®è¯" regexpError: "æ£åˆ™è¡¨è¾¾å¼é”™è¯¯" regexpErrorDescription: "{tab} å±è”½æ–‡å—的第 {line} 行的æ£åˆ™è¡¨è¾¾å¼æœ‰é”™è¯¯ï¼š" -instanceMute: "被å±è”½çš„æœåŠ¡å™¨" +instanceMute: "å·²éšè—çš„æœåС噍" userSaysSomething: "{name} 说了什么,但是被å±è”½è¯è¿‡æ»¤äº†" makeActive: "å¯ç”¨" display: "显示" @@ -706,7 +706,7 @@ useGlobalSettingDesc: "å¯ç”¨æ—¶ï¼Œå°†ä½¿ç”¨è´¦æˆ·é€šçŸ¥è®¾ç½®ã€‚关闿—¶ï¼Œåˆ™ other: "å…¶ä»–" regenerateLoginToken: "釿–°ç”Ÿæˆç™»å½•令牌" regenerateLoginTokenDescription: "釿–°ç”Ÿæˆç”¨äºŽç™»å½•的内部令牌。通常您ä¸éœ€è¦è¿™æ ·åšã€‚釿–°ç”ŸæˆåŽï¼Œæ‚¨å°†åœ¨æ‰€æœ‰è®¾å¤‡ä¸Šç™»å‡ºã€‚" -theKeywordWhenSearchingForCustomEmoji: "这将是æœç´ è‡ªå®šä¹‰è¡¨æƒ…ç¬¦å·æ—¶çš„关键è¯ã€‚" +theKeywordWhenSearchingForCustomEmoji: "这将是æœç´¢è‡ªå®šä¹‰è¡¨æƒ…ç¬¦å·æ—¶çš„关键è¯ã€‚" setMultipleBySeparatingWithSpace: "您å¯ä»¥ä½¿ç”¨ç©ºæ ¼åˆ†éš”多个项目。" fileIdOrUrl: "文件 ID 或者 URL" behavior: "行为" @@ -856,9 +856,9 @@ user: "用户" administration: "管ç†" accounts: "账户" switch: "切æ¢" -noMaintainerInformationWarning: "管ç†äººå‘˜ä¿¡æ¯æœªè®¾ç½®ã€‚" +noMaintainerInformationWarning: "尚未设置管ç†å‘˜ä¿¡æ¯ã€‚" noInquiryUrlWarning: "尚未设置è”络地å€ã€‚" -noBotProtectionWarning: "Bot 防御未设置。" +noBotProtectionWarning: "尚未设置 Bot 防御。" configure: "设置" postToGallery: "å‘é€åˆ°å›¾åº“" postToHashtag: "æŠ•ç¨¿åˆ°è¿™ä¸ªæ ‡ç¾" @@ -874,11 +874,11 @@ priority: "优先级" high: "高" middle: "ä¸" low: "低" -emailNotConfiguredWarning: "电åé‚®ä»¶åœ°å€æœªè®¾ç½®ã€‚" +emailNotConfiguredWarning: "尚未设置电å邮件地å€ã€‚" ratio: "比率" previewNoteText: "预览文本" customCss: "自定义 CSS" -customCssWarn: "这些设置必须有相关的基础知识,ä¸å½“çš„é…ç½®å¯èƒ½å¯¼è‡´å®¢æˆ·ç«¯æ— 法æ£å¸¸ä½¿ç”¨ï¼" +customCssWarn: "这些设置必须有相关的基础知识,ä¸å½“çš„é…ç½®å¯èƒ½å¯¼è‡´å®¢æˆ·ç«¯æ— 法æ£å¸¸ä½¿ç”¨ã€‚" global: "全局" squareAvatars: "显示方形头åƒå›¾æ ‡" sent: "å‘é€" @@ -915,8 +915,8 @@ manageAccounts: "管ç†è´¦æˆ·" makeReactionsPublic: "将回应设置为公开" makeReactionsPublicDescription: "将您å‘表过的回应设置æˆå…¬å¼€å¯è§ã€‚" classic: "ç»å…¸" -muteThread: "å±è”½å¸–å列表" -unmuteThread: "å–æ¶ˆå±è”½å¸–å列表" +muteThread: "éšè—帖å列表" +unmuteThread: "å–æ¶ˆéšè—帖å列表" followingVisibility: "关注的人的公开范围" followersVisibility: "关注者的公开范围" continueThread: "查看更多帖å" @@ -939,7 +939,7 @@ searchByGoogle: "Google" instanceDefaultLightTheme: "æœåŠ¡å™¨é»˜è®¤æµ…è‰²ä¸»é¢˜" instanceDefaultDarkTheme: "æœåŠ¡å™¨é»˜è®¤æ·±è‰²ä¸»é¢˜" instanceDefaultThemeDescription: "ä»¥å¯¹è±¡æ ¼å¼è¾“入主题代ç " -mutePeriod: "å±è”½æœŸé™" +mutePeriod: "éšè—期é™" period: "æˆªæ¢æ—¶é—´" indefinitely: "永久" tenMinutes: "10 分钟" @@ -947,6 +947,9 @@ oneHour: "1 å°æ—¶" oneDay: "1 天" oneWeek: "1 周" oneMonth: "1 个月" +threeMonths: "3 个月" +oneYear: "1 å¹´" +threeDays: "3 天" reflectMayTakeTime: "å¯èƒ½éœ€è¦ä¸€äº›æ—¶é—´æ‰èƒ½ä½“现出效果。" failedToFetchAccountInformation: "获å–账户信æ¯å¤±è´¥" rateLimitExceeded: "已超过速率é™åˆ¶" @@ -1054,7 +1057,7 @@ internalServerErrorDescription: "内部æœåС噍å‘生了预期外的错误" copyErrorInfo: "å¤åˆ¶é”™è¯¯ä¿¡æ¯" joinThisServer: "在本æœåŠ¡å™¨ä¸Šæ³¨å†Œ" exploreOtherServers: "探索其他æœåС噍" -letsLookAtTimeline: "时间线" +letsLookAtTimeline: "看看时间线" disableFederationConfirm: "确定è¦ç¦ç”¨è”åˆï¼Ÿ" disableFederationConfirmWarn: "ç¦ç”¨è”åˆä¸ä¼šå°†å¸–åè®¾ä¸ºç§æœ‰ã€‚在大多数情况下,ä¸éœ€è¦ç¦ç”¨è”åˆã€‚" disableFederationOk: "è”åˆç¦ç”¨" @@ -1070,10 +1073,10 @@ nonSensitiveOnlyForLocalLikeOnlyForRemote: "ä»…é™éžæ•感内容(远程仅点 rolesAssignedToMe: "指派给自己的角色" resetPasswordConfirm: "确定é‡ç½®å¯†ç ?" sensitiveWords: "æ•æ„Ÿè¯" -sensitiveWordsDescription: "将包å«è®¾ç½®è¯çš„帖åçš„å¯è§èŒƒå›´è®¾ç½®ä¸ºé¦–页。å¯ä»¥é€šè¿‡ç”¨æ¢è¡Œç¬¦åˆ†éš”æ¥è®¾ç½®å¤šä¸ªã€‚" +sensitiveWordsDescription: "包å«è¿™äº›è¯çš„帖åå°†åªåœ¨é¦–页å¯è§ã€‚å¯ç”¨æ¢è¡Œæ¥è®¾å®šå¤šä¸ªè¯ã€‚" sensitiveWordsDescription2: "AND æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”,æ£åˆ™è¡¨è¾¾å¼ç”¨æ–œçº¿åŒ…裹。" prohibitedWords: "ç¦ç”¨è¯" -prohibitedWordsDescription: "å‘布包å«è®¾å®šè¯æ±‡çš„取志¶å°†å‡ºé”™ã€‚å¯ç”¨æ¢è¡Œè®¾å®šå¤šä¸ªå…³é”®å—" +prohibitedWordsDescription: "å‘布包å«è®¾å®šè¯æ±‡çš„取志¶å°†å‡ºé”™ã€‚å¯ç”¨æ¢è¡Œè®¾å®šå¤šä¸ªå…³é”®å—。" prohibitedWordsDescription2: "AND æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”,æ£åˆ™è¡¨è¾¾å¼ç”¨æ–œçº¿åŒ…裹。" hiddenTags: "éšè—æ ‡ç¾" hiddenTagsDescription: "è®¾å®šçš„æ ‡ç¾å°†ä¸ä¼šåœ¨æ—¶é—´çº¿ä¸Šæ˜¾ç¤ºã€‚å¯ä½¿ç”¨æ¢è¡Œæ¥è®¾ç½®å¤šä¸ªæ ‡ç¾ã€‚" @@ -1116,7 +1119,7 @@ vertical: "纵å‘" horizontal: "横å‘" position: "ä½ç½®" serverRules: "æœåŠ¡å™¨è§„åˆ™" -pleaseConfirmBelowBeforeSignup: "在这个æœåŠ¡å™¨ä¸Šæ³¨å†Œè´¦å·å‰ï¼Œè¯·ç¡®è®¤ä»¥ä¸‹ä¿¡æ¯ã€‚" +pleaseConfirmBelowBeforeSignup: "如果è¦åœ¨æ¤æœåŠ¡å™¨ä¸Šæ³¨å†Œï¼Œéœ€è¦ç¡®è®¤å¹¶åŒæ„以下内容。" pleaseAgreeAllToContinue: "å¿…é¡»å…¨éƒ¨å‹¾é€‰ã€ŒåŒæ„ã€æ‰èƒ½å¤Ÿç»§ç»ã€‚" continue: "ç»§ç»" preservedUsernames: "ä¿ç•™çš„用户å" @@ -1156,10 +1159,10 @@ turnOffToImprovePerformance: "å…³é—该选项å¯ä»¥æé«˜æ€§èƒ½ã€‚" createInviteCode: "生æˆé‚€è¯·ç " createWithOptions: "使用选项æ¥åˆ›å»º" createCount: "å‘行数" -inviteCodeCreated: "已创建邀请ç " -inviteLimitExceeded: "å¯ä¾›å‘行的邀请ç 已达上é™ã€‚" -createLimitRemaining: "å¯ä¾›å‘行的邀请ç :剩余{limit}个" -inviteLimitResetCycle: "å¯ä»¥åœ¨{time}内å‘行最多{limit}个邀请ç 。" +inviteCodeCreated: "已生æˆé‚€è¯·ç " +inviteLimitExceeded: "å¯ä¾›ç”Ÿæˆçš„邀请ç 已达上é™ã€‚" +createLimitRemaining: "å¯ä¾›ç”Ÿæˆçš„邀请ç :剩余 {limit} 个" +inviteLimitResetCycle: "å¯ä»¥åœ¨ {time} å†…ç”Ÿæˆæœ€å¤š {limit} 个邀请ç 。" expirationDate: "有效日期" noExpirationDate: "ä¸è®¾ç½®æœ‰æ•ˆæ—¥æœŸ" inviteCodeUsedAt: "邀请ç 被使用的日期和时间" @@ -1293,6 +1296,23 @@ prohibitedWordsForNameOfUser: "用户åä¸ç¦æ¢çš„è¯" prohibitedWordsForNameOfUserDescription: "æ›´æ”¹ç”¨æˆ·åæ—¶ï¼Œå¦‚果用户åä¸åŒ…嫿¤åˆ—è¡¨é‡Œçš„è¯æ±‡ï¼Œç”¨æˆ·çš„æ”¹å请求将被拒ç»ã€‚æŒæœ‰ç®¡ç†å‘˜æƒé™çš„用户ä¸å—æ¤é™åˆ¶ã€‚" yourNameContainsProhibitedWords: "ç›®æ ‡ç”¨æˆ·å包å«è¿ç¦è¯" yourNameContainsProhibitedWordsDescription: "用户å内嫿œ‰è¿ç¦è¯ã€‚若想使用æ¤ç”¨æˆ·å,请è”ç³»æœåŠ¡å™¨ç®¡ç†å‘˜ã€‚" +thisContentsAreMarkedAsSigninRequiredByAuthor: "æ ¹æ®å‘帖者的设定,需è¦ç™»å½•æ‰èƒ½æ˜¾ç¤º" +lockdown: "é”定" +pleaseSelectAccount: "è¯·é€‰æ‹©å¸æˆ·" +availableRoles: "å¯ç”¨è§’色" +acknowledgeNotesAndEnable: "ç†è§£æ³¨æ„事项åŽå†å¼€å¯ã€‚" +_accountSettings: + requireSigninToViewContents: "需è¦ç™»å½•æ‰èƒ½æ˜¾ç¤ºå†…容" + requireSigninToViewContentsDescription1: "您å‘布的所有帖åå°†å˜æˆéœ€è¦ç™»å…¥åŽæ‰ä¼šæ˜¾ç¤ºã€‚有望防æ¢çˆ¬è™«æ”¶é›†å„ç§ä¿¡æ¯ã€‚" + requireSigninToViewContentsDescription2: "没有 URL 预览(OGP)ã€å†…嵌网页ã€å¼•用帖å的功能的æœåŠ¡å™¨ä¹Ÿå°†æ— æ³•æ˜¾ç¤ºã€‚" + requireSigninToViewContentsDescription3: "这些é™åˆ¶å¯èƒ½ä¸é€‚用于è”åˆåˆ°è¿œç¨‹æœåŠ¡å™¨çš„å†…å®¹ã€‚" + makeNotesFollowersOnlyBefore: "å¯å°†è¿‡åŽ»çš„å¸–å设为仅关注者å¯è§" + makeNotesFollowersOnlyBeforeDescription: "开坿¤è®¾å®šæ—¶ï¼Œè¶…过设定的时间或日期åŽï¼Œå¸–åå°†å˜ä¸ºä»…关注者å¯è§ã€‚å…³é—åŽå¸–å的公开状æ€å°†æ¢å¤æˆåŽŸæœ¬çš„è®¾å®šã€‚" + makeNotesHiddenBefore: "将过去的帖å设为ç§å¯†" + makeNotesHiddenBeforeDescription: "开坿¤è®¾å®šæ—¶ï¼Œè¶…过设定的时间或日期åŽï¼Œå¸–åå°†å˜ä¸ºä»…自己å¯è§ã€‚å…³é—åŽå¸–å的公开状æ€å°†æ¢å¤æˆåŽŸæœ¬çš„è®¾å®šã€‚" + mayNotEffectForFederatedNotes: "与远程æœåС噍è”åˆçš„帖å在远端å¯èƒ½ä¼šæ²¡æœ‰æ•ˆæžœã€‚" + notesHavePassedSpecifiedPeriod: "超过指定时间的帖å" + notesOlderThanSpecifiedDateAndTime: "指定日期å‰çš„帖å" _abuseUserReport: forward: "转å‘" forwardDescription: "ç›®æ ‡æ˜¯åŒ¿å系统账户,将把举报转å‘给远程æœåŠ¡å™¨ã€‚" @@ -1408,8 +1428,8 @@ _initialTutorial: description: "对于æœåŠ¡å™¨æ–¹é’ˆæ‰€è¦æ±‚è¦æ±‚çš„ï¼Œåˆæˆ–者ä¸é€‚åˆç›´æŽ¥å±•ç¤ºçš„é™„ä»¶ï¼Œè¯·æ·»åŠ ã€Œæ•æ„Ÿã€æ ‡è®°ã€‚\n" tryThisFile: "è¯•è¯•çœ‹ï¼Œå°†é™„åŠ åˆ°æ¤çª—å£çš„å›¾åƒæ ‡æ³¨ä¸ºæ•感ï¼" _exampleNote: - note: "拆纳豆包装时出错了…" - method: "è¦æ ‡æ³¨é™„ä»¶ä¸ºæ•æ„Ÿå†…容,请å•击该文件以打开èœå•,然åŽå•å‡»â€œæ ‡è®°ä¸ºæ•æ„Ÿå†…容â€ã€‚" + note: "拆纳豆包装时失手了…" + method: "è¦æ ‡æ³¨é™„ä»¶ä¸ºæ•æ„Ÿå†…容,请å•击该文件以打开èœå•,然åŽå•å‡»ã€Œæ ‡è®°ä¸ºæ•æ„Ÿå†…容ã€ã€‚" sensitiveSucceeded: "é™„åŠ æ–‡ä»¶æ—¶ï¼Œè¯·éµå¾ªæœåŠ¡å™¨çš„æ¡æ¬¾æ¥è®¾ç½®æ£ç¡®æ•感设定。\n" doItToContinue: "å°†å›¾åƒæ ‡è®°ä¸ºæ•æ„ŸåŽæ‰èƒ½å¤Ÿç»§ç»" _done: @@ -1437,6 +1457,8 @@ _serverSettings: reactionsBufferingDescription: "开坿—¶å¯æ˜¾è‘—æé«˜å‘é€å›žåº”时的性能,åŠå‡è½»æ•°æ®åº“è´Ÿè·ã€‚但 Redis 的内å˜ç”¨é‡ä¼šç›¸åº”å¢žåŠ ã€‚" inquiryUrl: "è”络地å€" inquiryUrlDescription: "ç”¨æ¥æŒ‡å®šè¯¸å¦‚呿œåŠ¡è¿è¥å•†å’¨è¯¢çš„论å›åœ°å€ï¼Œæˆ–记载了è¿è¥å•†è”系方å¼ä¹‹ç±»çš„网页地å€ã€‚" + openRegistration: "开放注册" + openRegistrationWarning: "开放注册有风险。建议仅当能够æŒç»ç›‘控æœåŠ¡å™¨å¹¶åœ¨å‡ºçŽ°é—®é¢˜æ—¶èƒ½å¤Ÿç«‹å³å“åº”æ—¶æ‰æ‰“开它。" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "è‹¥åœ¨ä¸€æ®µæ—¶é—´å†…æ²¡æœ‰æ£€æµ‹åˆ°ç®¡ç†æ´»åŠ¨ï¼Œä¸ºé˜²æ¢åžƒåœ¾ä¿¡æ¯ï¼Œæ¤è®¾å®šå°†è‡ªåЍ关é—。" _accountMigration: moveFrom: "从别的账å·è¿ç§»åˆ°æ¤è´¦æˆ·" @@ -1685,9 +1707,9 @@ _achievements: description: "在元旦登入" flavor: "今年也请对本æœåŠ¡å™¨å¤šå¤šæŒ‡æ•™ï¼" _cookieClicked: - title: "ç‚¹å‡»é¥¼å¹²å°æ¸¸æˆ" + title: "饼干点点ä¹" description: "点击了饼干" - flavor: "用错软件了?" + flavor: "穿越了?" _brainDiver: title: "Brain Diver" description: "å‘å¸ƒäº†åŒ…å« Brain Diver 链接的帖å" @@ -1757,7 +1779,7 @@ _role: canUpdateBioMedia: "å¯ä»¥æ›´æ–°å¤´åƒå’Œæ¨ªå¹…" pinMax: "帖å置顶数é‡é™åˆ¶" antennaMax: "å¯åˆ›å»ºçš„æœ€å¤§å¤©çº¿æ•°é‡" - wordMuteMax: "å±è”½è¯çš„å—æ•°é™åˆ¶" + wordMuteMax: "éšè—è¯çš„å—æ•°é™åˆ¶" webhookMax: "Webhook 创建数é‡é™åˆ¶" clipMax: "便ç¾åˆ›å»ºæ•°é‡é™åˆ¶" noteEachClipsMax: "å•个便ç¾å†…的贴文数é‡é™åˆ¶" @@ -1770,7 +1792,7 @@ _role: canUseTranslator: "使用翻译功能" avatarDecorationLimit: "坿·»åŠ å¤´åƒæŒ‚件的最大个数" canImportAntennas: "å…许导入天线" - canImportBlocking: "å…许导入拉黑列表" + canImportBlocking: "å…许导入å±è”½åˆ—表" canImportFollowing: "å…许导入关注列表" canImportMuting: "å…许导入å±è”½åˆ—表" canImportUserLists: "å…许导入用户列表" @@ -1920,14 +1942,14 @@ _menuDisplay: top: "顶部" hide: "éšè—" _wordMute: - muteWords: "ç¦ç”¨è¯" + muteWords: "è¦éšè—çš„è¯" muteWordsDescription: "AND æ¡ä»¶ç”¨ç©ºæ ¼åˆ†éš”,OR æ¡ä»¶ç”¨æ¢è¡Œç¬¦åˆ†éš”。" muteWordsDescription2: "æ£åˆ™è¡¨è¾¾å¼ç”¨æ–œçº¿åŒ…裹" _instanceMute: - instanceMuteDescription: "å±è”½æœåС噍ä¸çš„æ‰€æœ‰å¸–å和转帖,包括这些æœåŠ¡å™¨ä¸Šçš„ç”¨æˆ·å›žå¤ã€‚" + instanceMuteDescription: "éšè—æœåС噍ä¸çš„æ‰€æœ‰å¸–å和转帖,包括这些æœåŠ¡å™¨ä¸Šçš„ç”¨æˆ·å›žå¤ã€‚" instanceMuteDescription2: "一行一个" title: "éšè—æœåŠ¡å™¨å·²è®¾ç½®çš„å¸–å。" - heading: "å±è”½æœåС噍" + heading: "å·²éšè—çš„æœåС噍" _theme: explore: "寻找主题" install: "安装主题" @@ -2067,8 +2089,8 @@ _2fa: _permissions: "read:account": "查看账户信æ¯" "write:account": "æ›´æ”¹å¸æˆ·ä¿¡æ¯" - "read:blocks": "查看黑åå•" - "write:blocks": "编辑黑åå•" + "read:blocks": "查看å±è”½åˆ—表" + "write:blocks": "编辑å±è”½åˆ—表" "read:drive": "查看网盘" "write:drive": "管ç†ç½‘盘文件" "read:favorites": "查看收è—夹" @@ -2077,8 +2099,8 @@ _permissions: "write:following": "关注/å–æ¶ˆå…³æ³¨" "read:messaging": "查看消æ¯" "write:messaging": "æ’°å†™æˆ–åˆ é™¤æ¶ˆæ¯" - "read:mutes": "查看å±è”½åˆ—表" - "write:mutes": "编辑å±è”½åˆ—表" + "read:mutes": "查看éšè—列表" + "write:mutes": "编辑éšè—列表" "write:notes": "æ’°å†™æˆ–åˆ é™¤å¸–å" "read:notifications": "查看通知" "write:notifications": "管ç†é€šçŸ¥" @@ -2157,8 +2179,11 @@ _auth: permissionAsk: "这个应用程åºéœ€è¦ä»¥ä¸‹æƒé™" pleaseGoBack: "请返回到应用程åº" callback: "回到应用程åº" + accepted: "å·²å…许访问" denied: "æ‹’ç»è®¿é—®" + scopeUser: "以下é¢çš„用户进行æ“作" pleaseLogin: "在对应用进行授æƒè®¸å¯ä¹‹å‰ï¼Œè¯·å…ˆç™»å½•" + byClickingYouWillBeRedirectedToThisUrl: "å…许访问åŽå°†ä¼šè‡ªåЍé‡å®šå‘到以下 URL" _antennaSources: all: "所有帖å" homeTimeline: "已关注用户的帖å" @@ -2275,8 +2300,8 @@ _exportOrImport: favoritedNotes: "æ”¶è—的帖å" clips: "便ç¾" followingList: "关注ä¸" - muteList: "å±è”½" - blockingList: "拉黑" + muteList: "éšè—" + blockingList: "å±è”½" userLists: "列表" excludeMutingUsers: "排除å±è”½ç”¨æˆ·" excludeInactiveUsers: "æŽ’é™¤ä¸æ´»è·ƒç”¨æˆ·" @@ -2710,3 +2735,12 @@ _embedCodeGen: generateCode: "生æˆåµŒå…¥ä»£ç " codeGenerated: "已生æˆä»£ç " codeGeneratedDescription: "将生æˆçš„代ç 贴到网站上æ¥ä½¿ç”¨ã€‚" +_selfXssPrevention: + warning: "è¦å‘Š" + title: "「在æ¤å¤„ç²˜è´´ä»€ä¹ˆä¸œè¥¿ã€æ˜¯æ¬ºè¯ˆè¡Œä¸ºã€‚" + description1: "如果在æ¤å¤„ç²˜è´´äº†ä»€ä¹ˆï¼Œæ¶æ„用户å¯èƒ½ä¼šæŽ¥ç®¡è´¦æˆ·æˆ–者盗å–个人资料。" + description2: "如果ä¸èƒ½å®Œå…¨ç†è§£å°†è¦ç²˜è´´çš„内容,%c 请立å³åœæ¢æ“作并关é—这个窗å£ã€‚" + description3: "详情请看这里。{link}" +_followRequest: + recieved: "已收到申请" + sent: "å·²å‘é€ç”³è¯·" diff --git a/locales/zh-TW.yml b/locales/zh-TW.yml index de18342bbf..d4ffb28c76 100644 --- a/locales/zh-TW.yml +++ b/locales/zh-TW.yml @@ -8,8 +8,8 @@ search: "æœå°‹" notifications: "通知" username: "使用者å稱" password: "密碼" -initialPasswordForSetup: "åˆå§‹è¨å®šç”¨çš„密碼" -initialPasswordIsIncorrect: "åˆå§‹è¨å®šç”¨çš„密碼錯誤。" +initialPasswordForSetup: "啟動åˆå§‹è¨å®šçš„密碼" +initialPasswordIsIncorrect: "啟動åˆå§‹è¨å®šçš„密碼錯誤。" initialPasswordForSetupDescription: "如果您自己安è£äº† Misskey,請使用您在è¨å®šæª”ä¸è¼¸å…¥çš„密碼。\n如果您使用 Misskey 的託管æœå‹™ä¹‹é¡žçš„æœå‹™ï¼Œè«‹ä½¿ç”¨æä¾›çš„密碼。\n如果您尚未è¨å®šå¯†ç¢¼ï¼Œè«‹å°‡å…¶ç•™ç©ºä¸¦ç¹¼çºŒã€‚" forgotPassword: "忘記密碼" fetchingAsApObject: "從è¯é‚¦å®‡å®™å–å¾—ä¸..." @@ -382,7 +382,6 @@ enableLocalTimeline: "啟用本地時間軸" enableGlobalTimeline: "啟用全域時間軸" disablingTimelinesInfo: "為了方便,å³ä½¿æ‚¨é—œé–‰äº†æ™‚間軸功能,管ç†å“¡å’Œå¯©æŸ¥å“¡ä»å¯ä»¥ç¹¼çºŒä½¿ç”¨ã€‚" registration: "註冊" -enableRegistration: "開放新使用者註冊" invite: "邀請" driveCapacityPerLocalAccount: "æ¯å€‹æœ¬åœ°ä½¿ç”¨è€…的雲端硬碟容é‡" driveCapacityPerRemoteAccount: "æ¯å€‹éžæœ¬åœ°ç”¨æˆ¶çš„雲端空間大å°" @@ -587,6 +586,7 @@ masterVolume: "主音é‡" notUseSound: "關閉音效" useSoundOnlyWhenActive: "ç€è¦½å™¨åœ¨å‰æ™¯é‹ä½œæ™‚,Misskey æ‰æœƒç™¼å‡ºéŸ³æ•ˆ" details: "詳細資訊" +renoteDetails: "轉發貼文的細節" chooseEmoji: "鏿“‡æ‚¨çš„表情符號" unableToProcess: "æ“作無法完æˆ" recentUsed: "最近使用" @@ -947,6 +947,9 @@ oneHour: "䏀尿™‚" oneDay: "一天" oneWeek: "一週" oneMonth: "一個月" +threeMonths: "3 個月" +oneYear: "1 å¹´" +threeDays: "3 æ—¥" reflectMayTakeTime: "å¯èƒ½éœ€è¦ä¸€äº›æ™‚é–“æ‰æœƒå‡ºç¾æ•ˆæžœã€‚" failedToFetchAccountInformation: "å–得帳戶資訊失敗" rateLimitExceeded: "已超éŽé€Ÿçއé™åˆ¶" @@ -1116,7 +1119,7 @@ vertical: "ç›´å‘" horizontal: "æ©«å‘" position: "ä½ç½®" serverRules: "伺æœå™¨è¦å‰‡" -pleaseConfirmBelowBeforeSignup: "在本伺æœå™¨è¨»å†Šä¹‹å‰ï¼Œè«‹ç¢ºèªä¸‹åˆ—äº‹é …ã€‚" +pleaseConfirmBelowBeforeSignup: "在本伺æœå™¨è¨»å†Šä¹‹å‰ï¼Œå¿…é ˆç¢ºèªä¸¦åŒæ„以下內容。" pleaseAgreeAllToContinue: "å¿…é ˆå…¨éƒ¨å‹¾é¸ã€ŒåŒæ„ã€æ‰èƒ½ç¹¼çºŒã€‚" continue: "繼續" preservedUsernames: "ä¿ç•™çš„使用者å稱" @@ -1293,6 +1296,23 @@ prohibitedWordsForNameOfUser: "ç¦æ¢ä½¿ç”¨çš„å—詞(使用者å稱)" prohibitedWordsForNameOfUserDescription: "如果使用者åç¨±åŒ…å«æ¤æ¸…å–®ä¸çš„任何å—ä¸²ï¼Œå‰‡æ‹’çµ•é‡æ–°å‘½å使用者。 具有審查員權é™çš„使用者ä¸å—æ¤é™åˆ¶çš„影響。" yourNameContainsProhibitedWords: "您嘗試更改的å稱包å«ç¦æ¢çš„å—串" yourNameContainsProhibitedWordsDescription: "å稱ä¸åŒ…å«ç¦æ¢ä½¿ç”¨çš„å—串。 如果您想使用æ¤å稱,請è¯çµ¡æ‚¨çš„伺æœå™¨ç®¡ç†å“¡ã€‚" +thisContentsAreMarkedAsSigninRequiredByAuthor: "作者將其è¨å®šç‚ºéœ€è¦ç™»å…¥æ‰èƒ½é¡¯ç¤ºã€‚" +lockdown: "鎖定" +pleaseSelectAccount: "è«‹é¸æ“‡å¸³æˆ¶" +availableRoles: "å¯ç”¨è§’色" +acknowledgeNotesAndEnable: "了解注æ„äº‹é …å¾Œå†é–‹å•Ÿã€‚" +_accountSettings: + requireSigninToViewContents: "é ˆç™»å…¥ä»¥é¡¯ç¤ºå…§å®¹" + requireSigninToViewContentsDescription1: "å¿…é ˆç™»å…¥æ‰æœƒé¡¯ç¤ºæ‚¨å»ºç«‹çš„貼文ç‰å…§å®¹ã€‚坿œ›æœ‰æ•ˆé˜²æ¢è³‡è¨Šè¢«çˆ¬èŸ²è’集。" + requireSigninToViewContentsDescription2: "ä¾†è‡ªä¸æ”¯æ´ URL é 覽 (OGP)〠網é 嵌入和引用貼文的伺æœå™¨ï¼Œä¹Ÿå°‡åœæ¢é¡¯ç¤ºã€‚" + requireSigninToViewContentsDescription3: "這些é™åˆ¶å¯èƒ½ä¸é©ç”¨æ–¼è¢«è¯é‚¦ç™¼é€è‡³é 端伺æœå™¨çš„內容。" + makeNotesFollowersOnlyBefore: "讓éŽåŽ»çš„è²¼æ–‡åƒ…å°è¿½éš¨è€…顯示" + makeNotesFollowersOnlyBeforeDescription: "啟用æ¤åŠŸèƒ½å¾Œï¼Œè¶…éŽè¨å®šçš„æ—¥æœŸå’Œæ™‚間或超éŽè¨å®šæ™‚間的貼文將僅å°è¿½éš¨è€…顯示。 å¦‚æžœæ‚¨å†æ¬¡åœç”¨å®ƒï¼Œè²¼æ–‡çš„公開狀態也會æ¢å¾©åŽŸç‹€ã€‚" + makeNotesHiddenBefore: "éš±è—éŽåŽ»çš„è²¼æ–‡" + makeNotesHiddenBeforeDescription: "啟用æ¤åŠŸèƒ½å¾Œï¼Œè¶…éŽè¨å®šçš„æ—¥æœŸå’Œæ™‚間或超éŽè¨å®šæ™‚間的貼文將僅å°è‡ªå·±é¡¯ç¤ºï¼ˆç§å¯†åŒ–)。 å¦‚æžœæ‚¨å†æ¬¡åœç”¨å®ƒï¼Œè²¼æ–‡çš„公開狀態也會æ¢å¾©åŽŸç‹€ã€‚" + mayNotEffectForFederatedNotes: "è¯é‚¦ç™¼é€è‡³é 端伺æœå™¨çš„貼文å¯èƒ½æœƒä¸å—影響。" + notesHavePassedSpecifiedPeriod: "早於指定時間的貼文" + notesOlderThanSpecifiedDateAndTime: "指定時間和日期之å‰çš„貼文" _abuseUserReport: forward: "轉發" forwardDescription: "以匿å系統帳戶將檢舉轉發至é 端伺æœå™¨ã€‚" @@ -1437,6 +1457,8 @@ _serverSettings: reactionsBufferingDescription: "啟用時,å¯ä»¥é¡¯è‘—æé«˜å»ºç«‹åæ‡‰æ™‚çš„æ•ˆèƒ½ä¸¦æ¸›å°‘è³‡æ–™åº«çš„è² è¼‰ã€‚ 但是,Redis è¨˜æ†¶é«”ä½¿ç”¨é‡æœƒå¢žåŠ ã€‚" inquiryUrl: "è¯çµ¡è¡¨å–®ç¶²å€" inquiryUrlDescription: "指定伺æœå™¨é‹ç‡Ÿè€…çš„è¯çµ¡è¡¨å–®ç¶²å€ï¼Œæˆ–包å«é‹ç‡Ÿè€…è¯çµ¡è³‡è¨Šç¶²é 的網å€ã€‚" + openRegistration: "å…許建立帳戶" + openRegistrationWarning: "開放註冊伴隨著風險。 建è°åªæœ‰åœ¨ä¼ºæœå™¨å—到æŒçºŒç›£æŽ§ï¼Œä¸¦æº–備好在出ç¾å•題時能立å³è™•ç†çš„æƒ…æ³ä¸‹æ‰é–‹æ”¾è¨»å†Šã€‚" thisSettingWillAutomaticallyOffWhenModeratorsInactive: "ç‚ºäº†é˜²æ¢ spamï¼Œå¦‚æžœä¸€æ®µæœŸé–“å…§æ²’æœ‰åµæ¸¬åˆ°å¯©æŸ¥å“¡çš„æ´»å‹•,æ¤è¨å®šå°‡è‡ªå‹•關閉。" _accountMigration: moveFrom: "從其他帳戶é·ç§»åˆ°é€™å€‹å¸³æˆ¶" @@ -2157,8 +2179,11 @@ _auth: permissionAsk: "æ¤æ‡‰ç”¨ç¨‹å¼éœ€è¦ä»¥ä¸‹æ¬Šé™" pleaseGoBack: "請返回至應用程å¼" callback: "回到應用程å¼" + accepted: "已授予å˜å–權é™" denied: "拒絕訪å•" + scopeUser: "以下列使用者身分æ“作" pleaseLogin: "å¿…é ˆç™»å…¥ä»¥æä¾›æ‡‰ç”¨ç¨‹å¼çš„å˜å–權é™ã€‚" + byClickingYouWillBeRedirectedToThisUrl: "如果授予å˜å–權é™ï¼Œå°±æœƒè‡ªå‹•å°Žå‘到以下的網å€" _antennaSources: all: "全部貼文" homeTimeline: "來自已追隨使用者的貼文" @@ -2416,7 +2441,7 @@ _notification: follow: "追隨ä¸" mention: "æåŠ" reply: "回覆" - renote: "轉發貼文" + renote: "轉發" quote: "引用" reaction: "忇‰" pollEnded: "å•å·èª¿æŸ¥çµæŸ" @@ -2710,3 +2735,12 @@ _embedCodeGen: generateCode: "建立嵌入程å¼ç¢¼" codeGenerated: "已產生程å¼ç¢¼" codeGeneratedDescription: "請將產生的程å¼ç¢¼è²¼åˆ°æ‚¨çš„網站上。" +_selfXssPrevention: + warning: "è¦å‘Š" + title: "「在æ¤ç•«é¢è²¼ä¸Šä¸€äº›å…§å®¹ã€å®Œå…¨æ˜¯å€‹é¨™å±€ã€‚" + description1: "如果您在æ¤è™•貼上任何內容,惡æ„使用者å¯èƒ½æœƒæŽ¥ç®¡æ‚¨çš„å¸³æˆ¶æˆ–ç«Šå–æ‚¨çš„個人資訊。" + description2: "如果您ä¸ç¢ºåˆ‡çŸ¥é“è¦è²¼ä¸Šçš„內容,%c è«‹ç«‹å³åœæ¢å·¥ä½œä¸¦é—œé–‰æ¤è¦–窗。" + description3: "細節請看這裡。{link}" +_followRequest: + recieved: "收到的請求" + sent: "é€å‡ºçš„請求" diff --git a/package.json b/package.json index 02782e5388..cad49748a4 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "sharkey", - "version": "2024.10.0-dev", + "version": "2024.11.1-rc", "codename": "shonk", "repository": { "type": "git", @@ -59,24 +59,24 @@ "fast-glob": "3.3.2", "ignore-walk": "6.0.5", "js-yaml": "4.1.0", - "postcss": "8.4.47", + "postcss": "8.4.49", "tar": "6.2.1", - "terser": "5.33.0", - "typescript": "5.6.2", - "esbuild": "0.23.1", + "terser": "5.36.0", + "typescript": "5.6.3", + "esbuild": "0.24.0", "glob": "11.0.0" }, "optionalDependencies": { - "cypress": "13.14.2" + "cypress": "13.15.2" }, "devDependencies": { "@misskey-dev/eslint-plugin": "2.0.3", - "@types/node": "20.14.12", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "cross-env": "7.0.3", - "eslint": "9.8.0", - "globals": "15.9.0", + "eslint": "9.14.0", + "globals": "15.12.0", "ncp": "2.0.0", "start-server-and-test": "2.0.8" } diff --git a/packages/backend/assets/tabler-badges/login-2.png b/packages/backend/assets/tabler-badges/login-2.png Binary files differnew file mode 100644 index 0000000000..f3ca8de3dd --- /dev/null +++ b/packages/backend/assets/tabler-badges/login-2.png diff --git a/packages/backend/eslint.config.js b/packages/backend/eslint.config.js index 7ee9953478..7c649a373c 100644 --- a/packages/backend/eslint.config.js +++ b/packages/backend/eslint.config.js @@ -12,7 +12,7 @@ export default [ languageOptions: { parserOptions: { parser: tsParser, - project: ['./tsconfig.json', './test/tsconfig.json'], + project: ['./tsconfig.json', './test/tsconfig.json', './test-federation/tsconfig.json'], sourceType: 'module', tsconfigRootDir: import.meta.dirname, }, diff --git a/packages/backend/jest.config.fed.cjs b/packages/backend/jest.config.fed.cjs new file mode 100644 index 0000000000..fae187bc23 --- /dev/null +++ b/packages/backend/jest.config.fed.cjs @@ -0,0 +1,13 @@ +/* + * For a detailed explanation regarding each configuration property and type check, visit: + * https://jestjs.io/docs/en/configuration.html + */ + +const base = require('./jest.config.cjs'); + +module.exports = { + ...base, + testMatch: [ + '<rootDir>/test-federation/test/**/*.test.ts', + ], +}; diff --git a/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js b/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js new file mode 100644 index 0000000000..4ff520172b --- /dev/null +++ b/packages/backend/migration/1727318020265-enableStatsForFederatedInstances.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class EnableStatsForFederatedInstances1727318020265 { + name = 'EnableStatsForFederatedInstances1727318020265' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableStatsForFederatedInstances" boolean NOT NULL DEFAULT true`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableStatsForFederatedInstances"`); + } +} diff --git a/packages/backend/migration/1728085812127-refine-abuse-user-report.js b/packages/backend/migration/1728085812127-refine-abuse-user-report.js new file mode 100644 index 0000000000..57cbfdcf6d --- /dev/null +++ b/packages/backend/migration/1728085812127-refine-abuse-user-report.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class RefineAbuseUserReport1728085812127 { + name = 'RefineAbuseUserReport1728085812127' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "moderationNote" character varying(8192) NOT NULL DEFAULT ''`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" ADD "resolvedAs" character varying(128)`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "resolvedAs"`); + await queryRunner.query(`ALTER TABLE "abuse_user_report" DROP COLUMN "moderationNote"`); + } +} diff --git a/packages/backend/migration/1728550878802-testcaptcha.js b/packages/backend/migration/1728550878802-testcaptcha.js new file mode 100644 index 0000000000..d8d987c0c1 --- /dev/null +++ b/packages/backend/migration/1728550878802-testcaptcha.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class Testcaptcha1728550878802 { + name = 'Testcaptcha1728550878802' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "enableTestcaptcha" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "enableTestcaptcha"`); + } +} diff --git a/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js b/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js new file mode 100644 index 0000000000..36e698d120 --- /dev/null +++ b/packages/backend/migration/1728634286056-prohibitedWordsForNameOfUser.js @@ -0,0 +1,14 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class ProhibitedWordsForNameOfUser1728634286056 { + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" ADD "prohibitedWordsForNameOfUser" character varying(1024) array NOT NULL DEFAULT '{}'`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "meta" DROP COLUMN "prohibitedWordsForNameOfUser"`); + } +} diff --git a/packages/backend/migration/1729333924409-signinRequiredForShowContents.js b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js new file mode 100644 index 0000000000..5d4d1fcce2 --- /dev/null +++ b/packages/backend/migration/1729333924409-signinRequiredForShowContents.js @@ -0,0 +1,16 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class SigninRequiredForShowContents1729333924409 { + name = 'SigninRequiredForShowContents1729333924409' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "requireSigninToViewContents" boolean NOT NULL DEFAULT false`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "requireSigninToViewContents"`); + } +} diff --git a/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js b/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js new file mode 100644 index 0000000000..5fe4886b04 --- /dev/null +++ b/packages/backend/migration/1729486255072-makeNotesHiddenBefore.js @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +export class MakeNotesHiddenBefore1729486255072 { + name = 'MakeNotesHiddenBefore1729486255072' + + async up(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesFollowersOnlyBefore" integer`); + await queryRunner.query(`ALTER TABLE "user" ADD "makeNotesHiddenBefore" integer`); + } + + async down(queryRunner) { + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesHiddenBefore"`); + await queryRunner.query(`ALTER TABLE "user" DROP COLUMN "makeNotesFollowersOnlyBefore"`); + } +} diff --git a/packages/backend/package.json b/packages/backend/package.json index 00baef56d8..702b788061 100644 --- a/packages/backend/package.json +++ b/packages/backend/package.json @@ -19,16 +19,18 @@ "watch": "node ./scripts/watch.mjs", "restart": "pnpm build && pnpm start", "dev": "node ./scripts/dev.mjs", - "typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit", - "eslint": "eslint --quiet \"{src,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache", + "typecheck": "pnpm --filter megalodon build && tsc --noEmit && tsc -p test --noEmit && tsc -p test-federation --noEmit", + "eslint": "eslint --quiet \"{src,test-federation,test,js,@types}/**/*.{js,jsx,ts,tsx,vue}\" --cache", "lint": "pnpm typecheck && pnpm eslint", "jest": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.unit.cjs", "jest:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.e2e.cjs", + "jest:fed": "node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --forceExit --config jest.config.fed.cjs", "jest-and-coverage": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.unit.cjs", "jest-and-coverage:e2e": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --coverage --forceExit --config jest.config.e2e.cjs", "jest-clear": "cross-env NODE_ENV=test node --experimental-vm-modules --experimental-import-meta-resolve node_modules/jest/bin/jest.js --clearCache", "test": "pnpm jest", "test:e2e": "pnpm build && pnpm build:test && pnpm jest:e2e", + "test:fed": "pnpm jest:fed", "test-and-coverage": "pnpm jest-and-coverage", "test-and-coverage:e2e": "pnpm build && pnpm build:test && pnpm jest-and-coverage:e2e", "generate-api-json": "node ./scripts/generate_api_json.js" @@ -65,32 +67,32 @@ "dependencies": { "@aws-sdk/client-s3": "3.620.0", "@aws-sdk/lib-storage": "3.620.0", - "@bull-board/api": "6.0.0", - "@bull-board/fastify": "6.0.0", - "@bull-board/ui": "6.0.0", + "@bull-board/api": "6.5.0", + "@bull-board/fastify": "6.5.0", + "@bull-board/ui": "6.5.0", "@discordapp/twemoji": "15.1.0", - "@fastify/accepts": "5.0.0", - "@fastify/cookie": "10.0.0", - "@fastify/cors": "10.0.0", - "@fastify/express": "4.0.0", - "@fastify/http-proxy": "10.0.0", - "@fastify/multipart": "9.0.0", - "@fastify/static": "8.0.0", - "@fastify/view": "10.0.0", + "@fastify/accepts": "5.0.1", + "@fastify/cookie": "11.0.1", + "@fastify/cors": "10.0.1", + "@fastify/express": "4.0.1", + "@fastify/http-proxy": "10.0.1", + "@fastify/multipart": "9.0.1", + "@fastify/static": "8.0.2", + "@fastify/view": "10.0.1", "@misskey-dev/sharp-read-bmp": "1.2.0", "@misskey-dev/summaly": "5.1.0", "@napi-rs/canvas": "0.1.56", - "@nestjs/common": "10.4.3", - "@nestjs/core": "10.4.3", - "@nestjs/testing": "10.4.3", + "@nestjs/common": "10.4.7", + "@nestjs/core": "10.4.7", + "@nestjs/testing": "10.4.7", "@peertube/http-signature": "1.7.0", - "@sentry/node": "8.20.0", - "@sentry/profiling-node": "8.20.0", + "@sentry/node": "8.38.0", + "@sentry/profiling-node": "8.38.0", "@simplewebauthn/server": "10.0.1", "@sinonjs/fake-timers": "11.2.2", "@smithy/node-http-handler": "2.5.0", "@swc/cli": "0.3.12", - "@swc/core": "1.6.6", + "@swc/core": "1.9.2", "@transfem-org/sfm-js": "0.24.5", "@twemoji/parser": "15.1.1", "@types/psl": "^1.1.3", @@ -102,7 +104,7 @@ "bcryptjs": "2.4.3", "blurhash": "2.0.5", "body-parser": "1.20.3", - "bullmq": "5.13.2", + "bullmq": "5.26.1", "cacheable-lookup": "7.0.0", "cbor": "9.0.2", "chalk": "5.3.0", @@ -118,12 +120,12 @@ "fastify-multer": "^2.0.3", "fastify-raw-body": "5.0.0", "feed": "4.2.2", - "file-type": "19.5.0", + "file-type": "19.6.0", "fluent-ffmpeg": "2.1.3", - "form-data": "4.0.0", + "form-data": "4.0.1", "glob": "11.0.0", - "got": "14.4.2", - "happy-dom": "15.7.4", + "got": "14.4.4", + "happy-dom": "15.11.4", "hpagent": "1.2.0", "htmlescape": "1.1.1", "http-link-header": "1.1.3", @@ -138,23 +140,24 @@ "jsrsasign": "11.1.0", "juice": "11.0.0", "megalodon": "workspace:*", - "meilisearch": "0.42.0", + "meilisearch": "0.45.0", + "juice": "11.0.0", "microformats-parser": "2.0.2", "mime-types": "2.1.35", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", "ms": "3.0.0-canary.1", - "nanoid": "5.0.7", + "nanoid": "5.0.8", "nested-property": "4.0.0", "node-fetch": "3.3.2", - "nodemailer": "6.9.15", + "nodemailer": "6.9.16", "oauth": "0.10.0", "oauth2orize": "1.12.0", "oauth2orize-pkce": "0.1.2", "os-utils": "0.0.14", - "otpauth": "9.3.2", - "parse5": "7.1.2", - "pg": "8.13.0", + "otpauth": "9.3.4", + "parse5": "7.2.1", + "pg": "8.13.1", "pkce-challenge": "4.1.0", "probe-image-size": "7.2.3", "promise-limit": "2.7.0", @@ -171,7 +174,7 @@ "rename": "1.0.4", "rss-parser": "3.13.0", "rxjs": "7.8.1", - "sanitize-html": "2.13.0", + "sanitize-html": "2.13.1", "secure-json-parse": "2.7.0", "sharp": "0.33.5", "slacc": "0.0.10", @@ -183,7 +186,7 @@ "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", "typeorm": "0.3.20", - "typescript": "5.6.2", + "typescript": "5.6.3", "ulid": "2.3.0", "uuid": "^9.0.1", "vary": "1.1.2", @@ -193,28 +196,28 @@ }, "devDependencies": { "@jest/globals": "29.7.0", - "@nestjs/platform-express": "10.4.3", + "@nestjs/platform-express": "10.4.7", "@simplewebauthn/types": "10.0.0", - "@swc/jest": "0.2.36", + "@swc/jest": "0.2.37", "@types/accepts": "1.3.7", - "@types/archiver": "6.0.2", + "@types/archiver": "6.0.3", "@types/bcryptjs": "2.4.6", "@types/body-parser": "1.19.5", - "@types/color-convert": "2.0.3", + "@types/color-convert": "2.0.4", "@types/content-disposition": "0.5.8", - "@types/fluent-ffmpeg": "2.1.26", + "@types/fluent-ffmpeg": "2.1.27", "@types/htmlescape": "1.1.3", "@types/http-link-header": "1.0.7", - "@types/jest": "29.5.13", + "@types/jest": "29.5.14", "@types/js-yaml": "4.0.9", "@types/jsdom": "21.1.7", "@types/jsonld": "1.5.15", "@types/jsrsasign": "10.5.14", "@types/mime-types": "2.1.4", "@types/ms": "0.7.34", - "@types/node": "20.14.12", + "@types/node": "22.9.0", "@types/nodemailer": "6.4.16", - "@types/oauth": "0.9.5", + "@types/oauth": "0.9.6", "@types/oauth2orize": "1.11.5", "@types/oauth2orize-pkce": "0.1.2", "@types/pg": "8.11.10", @@ -233,14 +236,14 @@ "@types/tmp": "0.2.6", "@types/uuid": "^9.0.4", "@types/vary": "1.1.3", - "@types/web-push": "3.6.3", - "@types/ws": "8.5.12", + "@types/web-push": "3.6.4", + "@types/ws": "8.5.13", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "aws-sdk-client-mock": "4.0.1", "cross-env": "7.0.3", "eslint-plugin-import": "2.30.0", - "execa": "9.4.0", + "execa": "8.0.1", "fkill": "9.0.0", "jest": "29.7.0", "jest-mock": "29.7.0", diff --git a/packages/backend/src/config.ts b/packages/backend/src/config.ts index 9cac058d96..4af1140f36 100644 --- a/packages/backend/src/config.ts +++ b/packages/backend/src/config.ts @@ -65,6 +65,8 @@ type Source = { publishTarballInsteadOfProvideRepositoryUrl?: boolean; + setupPassword?: string; + proxy?: string; proxySmtp?: string; proxyBypassHosts?: string[]; @@ -180,6 +182,7 @@ export type Config = { version: string; publishTarballInsteadOfProvideRepositoryUrl: boolean; + setupPassword: string | undefined; host: string; hostname: string; scheme: string; @@ -282,6 +285,7 @@ export function loadConfig(): Config { return { version, publishTarballInsteadOfProvideRepositoryUrl: !!config.publishTarballInsteadOfProvideRepositoryUrl, + setupPassword: config.setupPassword, url: url.origin, port: config.port ?? parseInt(process.env.PORT ?? '3000', 10), socket: config.socket, @@ -498,5 +502,5 @@ function applyEnvOverrides(config: Source) { _apply_top([['outgoingAddress', 'outgoingAddressFamily', 'proxy', 'proxySmtp', 'mediaProxy', 'proxyRemoteFiles', 'videoThumbnailGenerator']]); _apply_top([['maxFileSize', 'maxNoteLength', 'maxRemoteNoteLength', 'maxAltTextLength', 'maxRemoteAltTextLength', 'pidFile', 'filePermissionBits']]); _apply_top(['import', ['downloadTimeout', 'maxFileSize']]); - _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature']]); + _apply_top([['signToActivityPubGet', 'checkActivityPubGetSignature', 'setupPassword']]); } diff --git a/packages/backend/src/core/AbuseReportNotificationService.ts b/packages/backend/src/core/AbuseReportNotificationService.ts index fe2c63e7d6..742e2621fd 100644 --- a/packages/backend/src/core/AbuseReportNotificationService.ts +++ b/packages/backend/src/core/AbuseReportNotificationService.ts @@ -22,6 +22,7 @@ import { RoleService } from '@/core/RoleService.js'; import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { IdService } from './IdService.js'; @Injectable() @@ -42,6 +43,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { private emailService: EmailService, private moderationLogService: ModerationLogService, private globalEventService: GlobalEventService, + private userEntityService: UserEntityService, ) { this.redisForSub.on('message', this.onMessage); } @@ -59,7 +61,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { return; } - const moderatorIds = await this.roleService.getModeratorIds(true, true); + const moderatorIds = await this.roleService.getModeratorIds({ + includeAdmins: true, + excludeExpire: true, + }); for (const moderatorId of moderatorIds) { for (const abuseReport of abuseReports) { @@ -135,6 +140,26 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { return; } + const usersMap = await this.userEntityService.packMany( + [ + ...new Set([ + ...abuseReports.map(it => it.reporter ?? it.reporterId), + ...abuseReports.map(it => it.targetUser ?? it.targetUserId), + ...abuseReports.map(it => it.assignee ?? it.assigneeId), + ].filter(x => x != null)), + ], + null, + { schema: 'UserLite' }, + ).then(it => new Map(it.map(it => [it.id, it]))); + const convertedReports = abuseReports.map(it => { + return { + ...it, + reporter: usersMap.get(it.reporterId) ?? null, + targetUser: usersMap.get(it.targetUserId) ?? null, + assignee: it.assigneeId ? (usersMap.get(it.assigneeId) ?? null) : null, + }; + }); + const recipientWebhookIds = await this.fetchWebhookRecipients() .then(it => it .filter(it => it.isActive && it.systemWebhookId && it.method === 'webhook') @@ -142,7 +167,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { .filter(x => x != null)); for (const webhookId of recipientWebhookIds) { await Promise.all( - abuseReports.map(it => { + convertedReports.map(it => { return this.systemWebhookService.enqueueSystemWebhook( webhookId, type, @@ -263,8 +288,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { .log(updater, 'createAbuseReportNotificationRecipient', { recipientId: id, recipient: created, - }) - .then(); + }); return created; } @@ -302,8 +326,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { recipientId: params.id, before: beforeEntity, after: afterEntity, - }) - .then(); + }); return afterEntity; } @@ -324,8 +347,7 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { .log(updater, 'deleteAbuseReportNotificationRecipient', { recipientId: id, recipient: entity, - }) - .then(); + }); } /** @@ -348,7 +370,10 @@ export class AbuseReportNotificationService implements OnApplicationShutdown { } // モデレータ権é™ã®æœ‰ç„¡ã§é€šçŸ¥å…ˆè¨å®šã‚’振り分ã‘ã‚‹ - const authorizedUserIds = await this.roleService.getModeratorIds(true, true); + const authorizedUserIds = await this.roleService.getModeratorIds({ + includeAdmins: true, + excludeExpire: true, + }); const authorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>(); const unauthorizedUserRecipients = Array.of<MiAbuseReportNotificationRecipient>(); for (const recipient of userRecipients) { diff --git a/packages/backend/src/core/AbuseReportService.ts b/packages/backend/src/core/AbuseReportService.ts index 007c3f1bf9..0b022d3b08 100644 --- a/packages/backend/src/core/AbuseReportService.ts +++ b/packages/backend/src/core/AbuseReportService.ts @@ -20,8 +20,10 @@ export class AbuseReportService { constructor( @Inject(DI.abuseUserReportsRepository) private abuseUserReportsRepository: AbuseUserReportsRepository, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, + private idService: IdService, private abuseReportNotificationService: AbuseReportNotificationService, private queueService: QueueService, @@ -77,62 +79,98 @@ export class AbuseReportService { * - SystemWebhook * * @param params é€šå ±å†…å®¹. ã‚‚ã—複数件ã®é€šå ±ã«å¯¾å¿œã—ãŸæ™‚ã®ãŸã‚ã«ã€ã‚らã‹ã˜ã‚複数件を処ç†ã§ãã‚‹å‰æã§è€ƒãˆã‚‹ - * @param operator é€šå ±ã‚’å‡¦ç†ã—ãŸãƒ¦ãƒ¼ã‚¶ + * @param moderator é€šå ±ã‚’å‡¦ç†ã—ãŸãƒ¦ãƒ¼ã‚¶ * @see AbuseReportNotificationService.notify */ @bindThis public async resolve( params: { reportId: string; - forward: boolean; + resolvedAs: MiAbuseUserReport['resolvedAs']; }[], - operator: MiUser, + moderator: MiUser, ) { const paramsMap = new Map(params.map(it => [it.reportId, it])); const reports = await this.abuseUserReportsRepository.findBy({ id: In(params.map(it => it.reportId)), }); - const targetUserMap = new Map(); - for (const report of reports) { - const shouldForward = paramsMap.get(report.id)!.forward; - - if (shouldForward && report.targetUserHost != null) { - targetUserMap.set(report.id, await this.usersRepository.findOneByOrFail({ id: report.targetUserId })); - } else { - targetUserMap.set(report.id, null); - } - } - for (const report of reports) { // eslint-disable-next-line @typescript-eslint/no-non-null-assertion const ps = paramsMap.get(report.id)!; await this.abuseUserReportsRepository.update(report.id, { resolved: true, - assigneeId: operator.id, - forwarded: ps.forward && report.targetUserHost !== null, + assigneeId: moderator.id, + resolvedAs: ps.resolvedAs, }); - const targetUser = targetUserMap.get(report.id)!; - if (targetUser != null) { - const actor = await this.instanceActorService.getInstanceActor(); - - // eslint-disable-next-line - const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment); - const contextAssignedFlag = this.apRendererService.addContext(flag); - this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false); - } - this.moderationLogService - .log(operator, 'resolveAbuseReport', { + .log(moderator, 'resolveAbuseReport', { reportId: report.id, report: report, - forwarded: ps.forward && report.targetUserHost !== null, + resolvedAs: ps.resolvedAs, }); } return this.abuseUserReportsRepository.findBy({ id: In(reports.map(it => it.id)) }) .then(reports => this.abuseReportNotificationService.notifySystemWebhook(reports, 'abuseReportResolved')); } + + @bindThis + public async forward( + reportId: MiAbuseUserReport['id'], + moderator: MiUser, + ) { + const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId }); + + if (report.targetUserHost == null) { + throw new Error('The target user host is null.'); + } + + if (report.forwarded) { + throw new Error('The report has already been forwarded.'); + } + + await this.abuseUserReportsRepository.update(report.id, { + forwarded: true, + }); + + const actor = await this.instanceActorService.getInstanceActor(); + const targetUser = await this.usersRepository.findOneByOrFail({ id: report.targetUserId }); + + const flag = this.apRendererService.renderFlag(actor, targetUser.uri!, report.comment); + const contextAssignedFlag = this.apRendererService.addContext(flag); + this.queueService.deliver(actor, contextAssignedFlag, targetUser.inbox, false); + + this.moderationLogService + .log(moderator, 'forwardAbuseReport', { + reportId: report.id, + report: report, + }); + } + + @bindThis + public async update( + reportId: MiAbuseUserReport['id'], + params: { + moderationNote?: MiAbuseUserReport['moderationNote']; + }, + moderator: MiUser, + ) { + const report = await this.abuseUserReportsRepository.findOneByOrFail({ id: reportId }); + + await this.abuseUserReportsRepository.update(report.id, { + moderationNote: params.moderationNote, + }); + + if (params.moderationNote != null && report.moderationNote !== params.moderationNote) { + this.moderationLogService.log(moderator, 'updateAbuseReportNote', { + reportId: report.id, + report: report, + before: report.moderationNote, + after: params.moderationNote, + }); + } + } } diff --git a/packages/backend/src/core/AccountMoveService.ts b/packages/backend/src/core/AccountMoveService.ts index 6e3125044c..24d11f29ff 100644 --- a/packages/backend/src/core/AccountMoveService.ts +++ b/packages/backend/src/core/AccountMoveService.ts @@ -274,13 +274,15 @@ export class AccountMoveService { } // Update instance stats by decreasing remote followers count by the number of local followers who were following the old account. - if (this.userEntityService.isRemoteUser(oldAccount)) { - this.federatedInstanceService.fetch(oldAccount.host).then(async i => { - this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateFollowers(i.host, false); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(oldAccount)) { + this.federatedInstanceService.fetchOrRegister(oldAccount.host).then(async i => { + this.instancesRepository.decrement({ id: i.id }, 'followersCount', localFollowerIds.length); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateFollowers(i.host, false); + } + }); + } } // FIXME: expensive? diff --git a/packages/backend/src/core/AnnouncementService.ts b/packages/backend/src/core/AnnouncementService.ts index 40a9db01c0..a9f6731977 100644 --- a/packages/backend/src/core/AnnouncementService.ts +++ b/packages/backend/src/core/AnnouncementService.ts @@ -72,7 +72,7 @@ export class AnnouncementService { updatedAt: null, title: values.title, text: values.text, - imageUrl: values.imageUrl, + imageUrl: values.imageUrl || null, icon: values.icon, display: values.display, forExistingUsers: values.forExistingUsers, @@ -209,6 +209,13 @@ export class AnnouncementService { return; } + const announcement = await this.announcementsRepository.findOneBy({ id: announcementId }); + if (announcement != null && announcement.userId === user.id) { + await this.announcementsRepository.update(announcementId, { + isActive: false, + }); + } + if ((await this.getUnreadAnnouncements(user)).length === 0) { this.globalEventService.publishMainStream(user.id, 'readAllAnnouncements'); } diff --git a/packages/backend/src/core/CaptchaService.ts b/packages/backend/src/core/CaptchaService.ts index 4be45dabb8..5b1ab00cfe 100644 --- a/packages/backend/src/core/CaptchaService.ts +++ b/packages/backend/src/core/CaptchaService.ts @@ -149,5 +149,18 @@ export class CaptchaService { throw new Error(`turnstile-failed: ${errorCodes}`); } } + + @bindThis + public async verifyTestcaptcha(response: string | null | undefined): Promise<void> { + if (response == null) { + throw new Error('testcaptcha-failed: no response provided'); + } + + const success = response === 'testcaptcha-passed'; + + if (!success) { + throw new Error('testcaptcha-failed'); + } + } } diff --git a/packages/backend/src/core/CoreModule.ts b/packages/backend/src/core/CoreModule.ts index b18db7f366..141f905d7f 100644 --- a/packages/backend/src/core/CoreModule.ts +++ b/packages/backend/src/core/CoreModule.ts @@ -14,6 +14,7 @@ import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationSe import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { UserSearchService } from '@/core/UserSearchService.js'; import { WebhookTestService } from '@/core/WebhookTestService.js'; +import { FlashService } from '@/core/FlashService.js'; import { TimeService } from '@/core/TimeService.js'; import { EnvService } from '@/core/EnvService.js'; import { AccountMoveService } from './AccountMoveService.js'; @@ -222,6 +223,7 @@ const $SystemWebhookService: Provider = { provide: 'SystemWebhookService', useEx const $WebhookTestService: Provider = { provide: 'WebhookTestService', useExisting: WebhookTestService }; const $UtilityService: Provider = { provide: 'UtilityService', useExisting: UtilityService }; const $FileInfoService: Provider = { provide: 'FileInfoService', useExisting: FileInfoService }; +const $FlashService: Provider = { provide: 'FlashService', useExisting: FlashService }; const $SearchService: Provider = { provide: 'SearchService', useExisting: SearchService }; const $ClipService: Provider = { provide: 'ClipService', useExisting: ClipService }; const $FeaturedService: Provider = { provide: 'FeaturedService', useExisting: FeaturedService }; @@ -375,6 +377,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp WebhookTestService, UtilityService, FileInfoService, + FlashService, SearchService, ClipService, FeaturedService, @@ -526,6 +529,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp $WebhookTestService, $UtilityService, $FileInfoService, + $FlashService, $SearchService, $ClipService, $FeaturedService, @@ -676,6 +680,7 @@ const $SponsorsService: Provider = { provide: 'SponsorsService', useExisting: Sp WebhookTestService, UtilityService, FileInfoService, + FlashService, SearchService, ClipService, FeaturedService, diff --git a/packages/backend/src/core/CustomEmojiService.ts b/packages/backend/src/core/CustomEmojiService.ts index cd906a72af..cc33fb5c0b 100644 --- a/packages/backend/src/core/CustomEmojiService.ts +++ b/packages/backend/src/core/CustomEmojiService.ts @@ -112,19 +112,33 @@ export class CustomEmojiService implements OnApplicationShutdown { } @bindThis - public async update(id: MiEmoji['id'], data: { + public async update(data: ( + { id: MiEmoji['id'], name?: string; } | { name: string; id?: MiEmoji['id'], } + ) & { driveFile?: MiDriveFile; - name?: string; category?: string | null; aliases?: string[]; license?: string | null; isSensitive?: boolean; localOnly?: boolean; roleIdsThatCanBeUsedThisEmojiAsReaction?: MiRole['id'][]; - }, moderator?: MiUser): Promise<void> { - const emoji = await this.emojisRepository.findOneByOrFail({ id: id }); - const sameNameEmoji = await this.emojisRepository.findOneBy({ name: data.name, host: IsNull() }); - if (sameNameEmoji != null && sameNameEmoji.id !== id) throw new Error('name already exists'); + }, moderator?: MiUser): Promise< + null + | 'NO_SUCH_EMOJI' + | 'SAME_NAME_EMOJI_EXISTS' + > { + const emoji = data.id + ? await this.getEmojiById(data.id) + : await this.getEmojiByName(data.name!); + if (emoji === null) return 'NO_SUCH_EMOJI'; + const id = emoji.id; + + // IDã¨çµµæ–‡å—åãŒä¸¡æ–¹æŒ‡å®šã•れã¦ã„ã‚‹å ´åˆã¯çµµæ–‡å—åã®å¤‰æ›´ã‚’行ã†ãŸã‚é‡è¤‡ãƒã‚§ãƒƒã‚¯ãŒå¿…è¦ + const doNameUpdate = data.id && data.name && (data.name !== emoji.name); + if (doNameUpdate) { + const isDuplicate = await this.checkDuplicate(data.name!); + if (isDuplicate) return 'SAME_NAME_EMOJI_EXISTS'; + } await this.emojisRepository.update(emoji.id, { updatedAt: new Date(), @@ -151,7 +165,7 @@ export class CustomEmojiService implements OnApplicationShutdown { const packed = await this.emojiEntityService.packDetailed(emoji.id); - if (emoji.name === data.name) { + if (!doNameUpdate) { this.globalEventService.publishBroadcastStream('emojiUpdated', { emojis: [packed], }); @@ -173,6 +187,7 @@ export class CustomEmojiService implements OnApplicationShutdown { after: updated, }); } + return null; } @bindThis diff --git a/packages/backend/src/core/FederatedInstanceService.ts b/packages/backend/src/core/FederatedInstanceService.ts index 7ec565557c..fca3ad847a 100644 --- a/packages/backend/src/core/FederatedInstanceService.ts +++ b/packages/backend/src/core/FederatedInstanceService.ts @@ -49,7 +49,7 @@ export class FederatedInstanceService implements OnApplicationShutdown { } @bindThis - public async fetch(host: string): Promise<MiInstance> { + public async fetchOrRegister(host: string): Promise<MiInstance> { host = this.utilityService.toPuny(host); const cached = await this.federatedInstanceCache.get(host); @@ -86,6 +86,24 @@ export class FederatedInstanceService implements OnApplicationShutdown { } @bindThis + public async fetch(host: string): Promise<MiInstance | null> { + host = this.utilityService.toPuny(host); + + const cached = await this.federatedInstanceCache.get(host); + if (cached !== undefined) return cached; + + const index = await this.instancesRepository.findOneBy({ host }); + + if (index == null) { + this.federatedInstanceCache.set(host, null); + return null; + } else { + this.federatedInstanceCache.set(host, index); + return index; + } + } + + @bindThis public async update(id: MiInstance['id'], data: Partial<MiInstance>): Promise<void> { const result = await this.instancesRepository.createQueryBuilder().update() .set(data) diff --git a/packages/backend/src/core/FetchInstanceMetadataService.ts b/packages/backend/src/core/FetchInstanceMetadataService.ts index aa16468ecb..987999bce7 100644 --- a/packages/backend/src/core/FetchInstanceMetadataService.ts +++ b/packages/backend/src/core/FetchInstanceMetadataService.ts @@ -82,7 +82,7 @@ export class FetchInstanceMetadataService { try { if (!force) { - const _instance = await this.federatedInstanceService.fetch(host); + const _instance = await this.federatedInstanceService.fetchOrRegister(host); const now = Date.now(); if (_instance && _instance.infoUpdatedAt && (now - _instance.infoUpdatedAt.getTime() < 1000 * 60 * 60 * 24)) { // unlock at the finally caluse diff --git a/packages/backend/src/core/FlashService.ts b/packages/backend/src/core/FlashService.ts new file mode 100644 index 0000000000..2a98225382 --- /dev/null +++ b/packages/backend/src/core/FlashService.ts @@ -0,0 +1,40 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { DI } from '@/di-symbols.js'; +import { type FlashsRepository } from '@/models/_.js'; + +/** + * MisskeyPlay関係ã®Service + */ +@Injectable() +export class FlashService { + constructor( + @Inject(DI.flashsRepository) + private flashRepository: FlashsRepository, + ) { + } + + /** + * 人気ã®ã‚ã‚‹Play一覧をå–å¾—ã™ã‚‹. + */ + public async featured(opts?: { offset?: number, limit: number }) { + const builder = this.flashRepository.createQueryBuilder('flash') + .andWhere('flash.likedCount > 0') + .andWhere('flash.visibility = :visibility', { visibility: 'public' }) + .addOrderBy('flash.likedCount', 'DESC') + .addOrderBy('flash.updatedAt', 'DESC') + .addOrderBy('flash.id', 'DESC'); + + if (opts?.offset) { + builder.skip(opts.offset); + } + + builder.take(opts?.limit ?? 10); + + return await builder.getMany(); + } +} diff --git a/packages/backend/src/core/NoteCreateService.ts b/packages/backend/src/core/NoteCreateService.ts index 35a2d8e290..96bb30a0d6 100644 --- a/packages/backend/src/core/NoteCreateService.ts +++ b/packages/backend/src/core/NoteCreateService.ts @@ -51,13 +51,13 @@ import { FeaturedService } from '@/core/FeaturedService.js'; import { FanoutTimelineService } from '@/core/FanoutTimelineService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { UserBlockingService } from '@/core/UserBlockingService.js'; -import { CacheService } from '@/core/CacheService.js'; import { isReply } from '@/misc/is-reply.js'; import { trackPromise } from '@/misc/promise-tracker.js'; import { isUserRelated } from '@/misc/is-user-related.js'; import { IdentifiableError } from '@/misc/identifiable-error.js'; import { LatestNoteService } from '@/core/LatestNoteService.js'; import { CollapsedQueue } from '@/misc/collapsed-queue.js'; +import { CacheService } from '@/core/CacheService.js'; type NotificationType = 'reply' | 'renote' | 'quote' | 'mention'; @@ -224,7 +224,7 @@ export class NoteCreateService implements OnApplicationShutdown { private cacheService: CacheService, private latestNoteService: LatestNoteService, ) { - this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount); + this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); } @bindThis @@ -413,7 +413,7 @@ export class NoteCreateService implements OnApplicationShutdown { } if (user.host && !data.cw) { - await this.federatedInstanceService.fetch(user.host).then(async i => { + await this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { if (i.isNSFW && !this.isPureRenote(data)) { data.cw = 'Instance is marked as NSFW'; } @@ -565,17 +565,17 @@ export class NoteCreateService implements OnApplicationShutdown { } // Register host - if (this.userEntityService.isRemoteUser(user)) { - this.federatedInstanceService.fetch(user.host).then(async i => { - if (note.renote && note.text) { - this.updateNotesCountQueue.enqueue(i.id, 1); - } else if (!note.renote) { - this.updateNotesCountQueue.enqueue(i.id, 1); - } - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateNote(i.host, note, true); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(user)) { + this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { + if (note.renote && note.text || !note.renote) { + this.updateNotesCountQueue.enqueue(i.id, 1); + } + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateNote(i.host, note, true); + } + }); + } } // ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æ›´æ–° @@ -608,13 +608,21 @@ export class NoteCreateService implements OnApplicationShutdown { this.followingsRepository.findBy({ followeeId: user.id, notify: 'normal', - }).then(followings => { + }).then(async followings => { if (note.visibility !== 'specified') { + const isPureRenote = this.isRenote(data) && !this.isQuote(data) ? true : false; for (const following of followings) { // TODO: ワードミュート考慮 - this.notificationService.createNotification(following.followerId, 'note', { - noteId: note.id, - }, user.id); + let isRenoteMuted = false; + if (isPureRenote) { + const userIdsWhoMeMutingRenotes = await this.cacheService.renoteMutingsCache.fetch(following.followerId); + isRenoteMuted = userIdsWhoMeMutingRenotes.has(user.id); + } + if (!isRenoteMuted) { + this.notificationService.createNotification(following.followerId, 'note', { + noteId: note.id, + }, user.id); + } } } }); diff --git a/packages/backend/src/core/NoteDeleteService.ts b/packages/backend/src/core/NoteDeleteService.ts index 285db9f152..b51a3143c9 100644 --- a/packages/backend/src/core/NoteDeleteService.ts +++ b/packages/backend/src/core/NoteDeleteService.ts @@ -115,25 +115,22 @@ export class NoteDeleteService { this.perUserNotesChart.update(user, note, false); } - if (note.renoteId && note.text) { - // Decrement notes count (user) - this.decNotesCountOfUser(user); - } else if (!note.renoteId) { + if (note.renoteId && note.text || !note.renoteId) { // Decrement notes count (user) this.decNotesCountOfUser(user); } - if (this.userEntityService.isRemoteUser(user)) { - this.federatedInstanceService.fetch(user.host).then(async i => { - if (note.renoteId && note.text) { - this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); - } else if (!note.renoteId) { - this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); - } - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateNote(i.host, note, false); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(user)) { + this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { + if (note.renoteId && note.text || !note.renoteId) { + this.instancesRepository.decrement({ id: i.id }, 'notesCount', 1); + } + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateNote(i.host, note, false); + } + }); + } } } diff --git a/packages/backend/src/core/NoteEditService.ts b/packages/backend/src/core/NoteEditService.ts index 406d134420..f1c7bcbea5 100644 --- a/packages/backend/src/core/NoteEditService.ts +++ b/packages/backend/src/core/NoteEditService.ts @@ -220,7 +220,7 @@ export class NoteEditService implements OnApplicationShutdown { private latestNoteService: LatestNoteService, private noteCreateService: NoteCreateService, ) { - this.updateNotesCountQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseNotesCount, this.performUpdateNotesCount); + this.updateNotesCountQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseNotesCount, this.performUpdateNotesCount); } @bindThis @@ -441,7 +441,7 @@ export class NoteEditService implements OnApplicationShutdown { } if (user.host && !data.cw) { - await this.federatedInstanceService.fetch(user.host).then(async i => { + await this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { if (i.isNSFW && !this.noteCreateService.isPureRenote(data)) { data.cw = 'Instance is marked as NSFW'; } @@ -592,13 +592,17 @@ export class NoteEditService implements OnApplicationShutdown { noindex: MiUser['noindex']; }, data: Option, silent: boolean, tags: string[], mentionedUsers: MinimumUser[]) { // Register host - if (this.userEntityService.isRemoteUser(user)) { - this.federatedInstanceService.fetch(user.host).then(async i => { - this.updateNotesCountQueue.enqueue(i.id, 1); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateNote(i.host, note, true); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(user)) { + this.federatedInstanceService.fetchOrRegister(user.host).then(async i => { + if (note.renote && note.text || !note.renote) { + this.updateNotesCountQueue.enqueue(i.id, 1); + } + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateNote(i.host, note, true); + } + }); + } } // ãƒãƒƒã‚·ãƒ¥ã‚¿ã‚°æ›´æ–° diff --git a/packages/backend/src/core/QueueService.ts b/packages/backend/src/core/QueueService.ts index d9d282a168..039c47724b 100644 --- a/packages/backend/src/core/QueueService.ts +++ b/packages/backend/src/core/QueueService.ts @@ -7,13 +7,15 @@ import { randomUUID } from 'node:crypto'; import { Inject, Injectable } from '@nestjs/common'; import type { IActivity } from '@/core/activitypub/type.js'; import type { MiDriveFile } from '@/models/DriveFile.js'; -import type { MiWebhook, webhookEventTypes } from '@/models/Webhook.js'; +import type { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js'; import type { MiSystemWebhook, SystemWebhookEventType } from '@/models/SystemWebhook.js'; import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import type { Antenna } from '@/server/api/endpoints/i/import-antennas.js'; import { ApRequestCreator } from '@/core/activitypub/ApRequestService.js'; +import { type SystemWebhookPayload } from '@/core/SystemWebhookService.js'; +import { type UserWebhookPayload } from './UserWebhookService.js'; import type { DbJobData, DeliverJobData, @@ -30,8 +32,8 @@ import type { ObjectStorageQueue, RelationshipQueue, SystemQueue, - UserWebhookDeliverQueue, SystemWebhookDeliverQueue, + UserWebhookDeliverQueue, ScheduleNotePostQueue, } from './QueueModule.js'; import type httpSignature from '@peertube/http-signature'; @@ -96,6 +98,13 @@ export class QueueService { repeat: { pattern: '0 0 * * *' }, removeOnComplete: true, }); + + this.systemQueue.add('checkModeratorsActivity', { + }, { + // 毎時30分ã«èµ·å‹• + repeat: { pattern: '30 * * * *' }, + removeOnComplete: true, + }); } @bindThis @@ -522,10 +531,10 @@ export class QueueService { * @see UserWebhookDeliverProcessorService */ @bindThis - public userWebhookDeliver( + public userWebhookDeliver<T extends WebhookEventTypes>( webhook: MiWebhook, - type: typeof webhookEventTypes[number], - content: unknown, + type: T, + content: UserWebhookPayload<T>, opts?: { attempts?: number }, ) { const data: UserWebhookDeliverJobData = { @@ -554,10 +563,10 @@ export class QueueService { * @see SystemWebhookDeliverProcessorService */ @bindThis - public systemWebhookDeliver( + public systemWebhookDeliver<T extends SystemWebhookEventType>( webhook: MiSystemWebhook, - type: SystemWebhookEventType, - content: unknown, + type: T, + content: SystemWebhookPayload<T>, opts?: { attempts?: number }, ) { const data: SystemWebhookDeliverJobData = { diff --git a/packages/backend/src/core/RoleService.ts b/packages/backend/src/core/RoleService.ts index 5651b04ac2..0bae3af385 100644 --- a/packages/backend/src/core/RoleService.ts +++ b/packages/backend/src/core/RoleService.ts @@ -107,6 +107,7 @@ export const DEFAULT_POLICIES: RolePolicies = { @Injectable() export class RoleService implements OnApplicationShutdown, OnModuleInit { + private rootUserIdCache: MemorySingleCache<MiUser['id']>; private rolesCache: MemorySingleCache<MiRole[]>; private roleAssignmentByUserIdCache: MemoryKVCache<MiRoleAssignment[]>; private notificationService: NotificationService; @@ -142,6 +143,7 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { private moderationLogService: ModerationLogService, private fanoutTimelineService: FanoutTimelineService, ) { + this.rootUserIdCache = new MemorySingleCache<MiUser['id']>(1000 * 60 * 60 * 24 * 7); // 1week. rootユーザã®IDã¯ä¸å¤‰ãªã®ã§é•·ã‚ã« this.rolesCache = new MemorySingleCache<MiRole[]>(1000 * 60 * 60); // 1h this.roleAssignmentByUserIdCache = new MemoryKVCache<MiRoleAssignment[]>(1000 * 60 * 5); // 5m @@ -425,49 +427,78 @@ export class RoleService implements OnApplicationShutdown, OnModuleInit { } @bindThis - public async isExplorable(role: { id: MiRole['id']} | null): Promise<boolean> { + public async isExplorable(role: { id: MiRole['id'] } | null): Promise<boolean> { if (role == null) return false; const check = await this.rolesRepository.findOneBy({ id: role.id }); if (check == null) return false; return check.isExplorable; } + /** + * モデレーター権é™ã®ãƒãƒ¼ãƒ«ãŒå‰²ã‚Šå½“ã¦ã‚‰ã‚Œã¦ã„るユーザID一覧をå–å¾—ã™ã‚‹. + * + * @param opts.includeAdmins 管ç†è€…権é™ã‚‚å«ã‚ã‚‹ã‹(デフォルト: true) + * @param opts.includeRoot rootユーザもå«ã‚ã‚‹ã‹(デフォルト: false) + * @param opts.excludeExpire 期é™åˆ‡ã‚Œã®ãƒãƒ¼ãƒ«ã‚’除外ã™ã‚‹ã‹(デフォルト: false) + */ @bindThis - public async getModeratorIds(includeAdmins = true, excludeExpire = false): Promise<MiUser['id'][]> { + public async getModeratorIds(opts?: { + includeAdmins?: boolean, + includeRoot?: boolean, + excludeExpire?: boolean, + }): Promise<MiUser['id'][]> { + const includeAdmins = opts?.includeAdmins ?? true; + const includeRoot = opts?.includeRoot ?? false; + const excludeExpire = opts?.excludeExpire ?? false; + const roles = await this.rolesCache.fetch(() => this.rolesRepository.findBy({})); const moderatorRoles = includeAdmins ? roles.filter(r => r.isModerator || r.isAdministrator) : roles.filter(r => r.isModerator); - // TODO: isRootãªã‚¢ã‚«ã‚¦ãƒ³ãƒˆã‚‚å«ã‚ã‚‹ const assigns = moderatorRoles.length > 0 ? await this.roleAssignmentsRepository.findBy({ roleId: In(moderatorRoles.map(r => r.id)) }) : []; + // Setを経由ã—ã¦é‡è¤‡ã‚’除去(ユーザIDã¯é‡è¤‡ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ã®ã§ï¼‰ const now = Date.now(); - const result = [ - // Setを経由ã—ã¦é‡è¤‡ã‚’除去(ユーザIDã¯é‡è¤‡ã™ã‚‹å¯èƒ½æ€§ãŒã‚ã‚‹ã®ã§ï¼‰ - ...new Set( - assigns - .filter(it => - (excludeExpire) - ? (it.expiresAt == null || it.expiresAt.getTime() > now) - : true, - ) - .map(a => a.userId), - ), - ]; + const resultSet = new Set( + assigns + .filter(it => + (excludeExpire) + ? (it.expiresAt == null || it.expiresAt.getTime() > now) + : true, + ) + .map(a => a.userId), + ); + + if (includeRoot) { + const rootUserId = await this.rootUserIdCache.fetch(async () => { + const it = await this.usersRepository.createQueryBuilder('users') + .select('id') + .where({ isRoot: true }) + .getRawOne<{ id: string }>(); + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + return it!.id; + }); + resultSet.add(rootUserId); + } - return result.sort((x, y) => x.localeCompare(y)); + return [...resultSet].sort((x, y) => x.localeCompare(y)); } @bindThis - public async getModerators(includeAdmins = true): Promise<MiUser[]> { - const ids = await this.getModeratorIds(includeAdmins); - const users = ids.length > 0 ? await this.usersRepository.findBy({ - id: In(ids), - }) : []; - return users; + public async getModerators(opts?: { + includeAdmins?: boolean, + includeRoot?: boolean, + excludeExpire?: boolean, + }): Promise<MiUser[]> { + const ids = await this.getModeratorIds(opts); + return ids.length > 0 + ? await this.usersRepository.findBy({ + id: In(ids), + }) + : []; } @bindThis diff --git a/packages/backend/src/core/SignupService.ts b/packages/backend/src/core/SignupService.ts index 3f0523b610..0ad448e95f 100644 --- a/packages/backend/src/core/SignupService.ts +++ b/packages/backend/src/core/SignupService.ts @@ -156,8 +156,8 @@ export class SignupService { })); }); - this.usersChart.update(account, true).then(); - this.userService.notifySystemWebhook(account, 'userCreated').then(); + this.usersChart.update(account, true); + this.userService.notifySystemWebhook(account, 'userCreated'); return { account, secret }; } diff --git a/packages/backend/src/core/SystemWebhookService.ts b/packages/backend/src/core/SystemWebhookService.ts index bb7c6b8c0e..de00169612 100644 --- a/packages/backend/src/core/SystemWebhookService.ts +++ b/packages/backend/src/core/SystemWebhookService.ts @@ -15,8 +15,39 @@ import { QueueService } from '@/core/QueueService.js'; import { ModerationLogService } from '@/core/ModerationLogService.js'; import { LoggerService } from '@/core/LoggerService.js'; import Logger from '@/logger.js'; +import { Packed } from '@/misc/json-schema.js'; +import { AbuseReportResolveType } from '@/models/AbuseUserReport.js'; +import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +export type AbuseReportPayload = { + id: string; + targetUserId: string; + targetUser: Packed<'UserLite'> | null; + targetUserHost: string | null; + reporterId: string; + reporter: Packed<'UserLite'> | null; + reporterHost: string | null; + assigneeId: string | null; + assignee: Packed<'UserLite'> | null; + resolved: boolean; + forwarded: boolean; + comment: string; + moderationNote: string; + resolvedAs: AbuseReportResolveType | null; +}; + +export type InactiveModeratorsWarningPayload = { + remainingTime: ModeratorInactivityRemainingTime; +}; + +export type SystemWebhookPayload<T extends SystemWebhookEventType> = + T extends 'abuseReport' | 'abuseReportResolved' ? AbuseReportPayload : + T extends 'userCreated' ? Packed<'UserLite'> : + T extends 'inactiveModeratorsWarning' ? InactiveModeratorsWarningPayload : + T extends 'inactiveModeratorsInvitationOnlyChanged' ? Record<string, never> : + never; + @Injectable() export class SystemWebhookService implements OnApplicationShutdown { private logger: Logger; @@ -101,8 +132,7 @@ export class SystemWebhookService implements OnApplicationShutdown { .log(updater, 'createSystemWebhook', { systemWebhookId: webhook.id, webhook: webhook, - }) - .then(); + }); return webhook; } @@ -139,8 +169,7 @@ export class SystemWebhookService implements OnApplicationShutdown { systemWebhookId: beforeEntity.id, before: beforeEntity, after: afterEntity, - }) - .then(); + }); return afterEntity; } @@ -158,8 +187,7 @@ export class SystemWebhookService implements OnApplicationShutdown { .log(updater, 'deleteSystemWebhook', { systemWebhookId: webhook.id, webhook, - }) - .then(); + }); } /** @@ -171,7 +199,7 @@ export class SystemWebhookService implements OnApplicationShutdown { public async enqueueSystemWebhook<T extends SystemWebhookEventType>( webhook: MiSystemWebhook | MiSystemWebhook['id'], type: T, - content: unknown, + content: SystemWebhookPayload<T>, ) { const webhookEntity = typeof webhook === 'string' ? (await this.fetchActiveSystemWebhooks()).find(a => a.id === webhook) diff --git a/packages/backend/src/core/UserFollowingService.ts b/packages/backend/src/core/UserFollowingService.ts index 77e7b60bea..8963003057 100644 --- a/packages/backend/src/core/UserFollowingService.ts +++ b/packages/backend/src/core/UserFollowingService.ts @@ -305,20 +305,22 @@ export class UserFollowingService implements OnModuleInit { //#endregion //#region Update instance stats - if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - this.federatedInstanceService.fetch(follower.host).then(async i => { - this.instancesRepository.increment({ id: i.id }, 'followingCount', 1); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateFollowing(i.host, true); - } - }); - } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - this.federatedInstanceService.fetch(followee.host).then(async i => { - this.instancesRepository.increment({ id: i.id }, 'followersCount', 1); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateFollowers(i.host, true); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { + this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => { + this.instancesRepository.increment({ id: i.id }, 'followingCount', 1); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateFollowing(i.host, true); + } + }); + } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { + this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => { + this.instancesRepository.increment({ id: i.id }, 'followersCount', 1); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateFollowers(i.host, true); + } + }); + } } //#endregion @@ -437,20 +439,22 @@ export class UserFollowingService implements OnModuleInit { //#endregion //#region Update instance stats - if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { - this.federatedInstanceService.fetch(follower.host).then(async i => { - this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateFollowing(i.host, false); - } - }); - } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { - this.federatedInstanceService.fetch(followee.host).then(async i => { - this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.updateFollowers(i.host, false); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + if (this.userEntityService.isRemoteUser(follower) && this.userEntityService.isLocalUser(followee)) { + this.federatedInstanceService.fetchOrRegister(follower.host).then(async i => { + this.instancesRepository.decrement({ id: i.id }, 'followingCount', 1); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateFollowing(i.host, false); + } + }); + } else if (this.userEntityService.isLocalUser(follower) && this.userEntityService.isRemoteUser(followee)) { + this.federatedInstanceService.fetchOrRegister(followee.host).then(async i => { + this.instancesRepository.decrement({ id: i.id }, 'followersCount', 1); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.updateFollowers(i.host, false); + } + }); + } } //#endregion diff --git a/packages/backend/src/core/UserWebhookService.ts b/packages/backend/src/core/UserWebhookService.ts index 8a40a53688..911efdf768 100644 --- a/packages/backend/src/core/UserWebhookService.ts +++ b/packages/backend/src/core/UserWebhookService.ts @@ -6,11 +6,23 @@ import { Inject, Injectable } from '@nestjs/common'; import * as Redis from 'ioredis'; import { type WebhooksRepository } from '@/models/_.js'; -import { MiWebhook } from '@/models/Webhook.js'; +import { MiWebhook, WebhookEventTypes } from '@/models/Webhook.js'; import { DI } from '@/di-symbols.js'; import { bindThis } from '@/decorators.js'; import { GlobalEvents } from '@/core/GlobalEventService.js'; import type { OnApplicationShutdown } from '@nestjs/common'; +import type { Packed } from '@/misc/json-schema.js'; + +export type UserWebhookPayload<T extends WebhookEventTypes> = + T extends 'note' | 'reply' | 'renote' |'mention' | 'edited' ? { + note: Packed<'Note'>, + } : + T extends 'follow' | 'unfollow' ? { + user: Packed<'UserDetailedNotMe'>, + } : + T extends 'followed' ? { + user: Packed<'UserLite'>, + } : never; @Injectable() export class UserWebhookService implements OnApplicationShutdown { diff --git a/packages/backend/src/core/UtilityService.ts b/packages/backend/src/core/UtilityService.ts index dda4bd5fbd..f905914022 100644 --- a/packages/backend/src/core/UtilityService.ts +++ b/packages/backend/src/core/UtilityService.ts @@ -123,6 +123,7 @@ export class UtilityService { return host; } + @bindThis private specialSuffix(hostname: string): string | null { // masto.host provides domain names for its clients, we have to // treat it as if it were a public suffix @@ -143,6 +144,7 @@ export class UtilityService { return host; } + @bindThis public isFederationAllowedHost(host: string): boolean { if (this.meta.federation === 'none') return false; if (this.meta.federation === 'specified' && !this.meta.federationHosts.some(x => `.${host.toLowerCase()}`.endsWith(`.${x}`))) return false; diff --git a/packages/backend/src/core/WebAuthnService.ts b/packages/backend/src/core/WebAuthnService.ts index 75ab0a207c..ad53192f18 100644 --- a/packages/backend/src/core/WebAuthnService.ts +++ b/packages/backend/src/core/WebAuthnService.ts @@ -246,14 +246,12 @@ export class WebAuthnService { @bindThis public async verifyAuthentication(userId: MiUser['id'], response: AuthenticationResponseJSON): Promise<boolean> { - const challenge = await this.redisClient.get(`webauthn:challenge:${userId}`); + const challenge = await this.redisClient.getdel(`webauthn:challenge:${userId}`); if (!challenge) { throw new IdentifiableError('2d16e51c-007b-4edd-afd2-f7dd02c947f6', 'challenge not found'); } - await this.redisClient.del(`webauthn:challenge:${userId}`); - const key = await this.userSecurityKeysRepository.findOneBy({ id: response.id, userId: userId, diff --git a/packages/backend/src/core/WebhookTestService.ts b/packages/backend/src/core/WebhookTestService.ts index a72a82ede6..dfe7a259c4 100644 --- a/packages/backend/src/core/WebhookTestService.ts +++ b/packages/backend/src/core/WebhookTestService.ts @@ -7,16 +7,17 @@ import { Injectable } from '@nestjs/common'; import { MiAbuseUserReport, MiNote, MiUser, MiWebhook } from '@/models/_.js'; import { bindThis } from '@/decorators.js'; import { MiSystemWebhook, type SystemWebhookEventType } from '@/models/SystemWebhook.js'; -import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { AbuseReportPayload, SystemWebhookPayload, SystemWebhookService } from '@/core/SystemWebhookService.js'; import { Packed } from '@/misc/json-schema.js'; import { type WebhookEventTypes } from '@/models/Webhook.js'; -import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { type UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { QueueService } from '@/core/QueueService.js'; +import { ModeratorInactivityRemainingTime } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; const oneDayMillis = 24 * 60 * 60 * 1000; -function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUserReport { - return { +function generateAbuseReport(override?: Partial<MiAbuseUserReport>): AbuseReportPayload { + const result: MiAbuseUserReport = { id: 'dummy-abuse-report1', targetUserId: 'dummy-target-user', targetUser: null, @@ -29,8 +30,17 @@ function generateAbuseReport(override?: Partial<MiAbuseUserReport>): MiAbuseUser comment: 'This is a dummy report for testing purposes.', targetUserHost: null, reporterHost: null, + resolvedAs: null, + moderationNote: 'foo', ...override, }; + + return { + ...result, + targetUser: result.targetUser ? toPackedUserLite(result.targetUser) : null, + reporter: result.reporter ? toPackedUserLite(result.reporter) : null, + assignee: result.assignee ? toPackedUserLite(result.assignee) : null, + }; } function generateDummyUser(override?: Partial<MiUser>): MiUser { @@ -73,6 +83,9 @@ function generateDummyUser(override?: Partial<MiUser>): MiUser { isExplorable: true, isHibernated: false, isDeleted: false, + requireSigninToViewContents: false, + makeNotesFollowersOnlyBefore: null, + makeNotesHiddenBefore: null, emojis: [], score: 0, host: null, @@ -289,7 +302,8 @@ const dummyUser3 = generateDummyUser({ @Injectable() export class WebhookTestService { - public static NoSuchWebhookError = class extends Error {}; + public static NoSuchWebhookError = class extends Error { + }; constructor( private userWebhookService: UserWebhookService, @@ -307,10 +321,10 @@ export class WebhookTestService { * - é€ä¿¡å¯¾è±¡ã‚¤ãƒ™ãƒ³ãƒˆï¼ˆon)ã«é–¢ã™ã‚‹è¨å®š */ @bindThis - public async testUserWebhook( + public async testUserWebhook<T extends WebhookEventTypes>( params: { webhookId: MiWebhook['id'], - type: WebhookEventTypes, + type: T, override?: Partial<Omit<MiWebhook, 'id'>>, }, sender: MiUser | null, @@ -322,7 +336,7 @@ export class WebhookTestService { } const webhook = webhooks[0]; - const send = (contents: unknown) => { + const send = <U extends WebhookEventTypes>(type: U, contents: UserWebhookPayload<U>) => { const merged = { ...webhook, ...params.override, @@ -330,7 +344,7 @@ export class WebhookTestService { // テスト目的ãªã®ã§UserWebhookServiceã®æ©Ÿèƒ½ã‚’経由ã›ãšç›´æŽ¥ã‚ューã«è¿½åŠ ã™ã‚‹ï¼ˆãƒã‚§ãƒƒã‚¯å‡¦ç†ãªã©ã‚’スã‚ップã™ã‚‹æ„図). // ã¾ãŸã€Jobã®è©¦è¡Œå›žæ•°ã‚‚1回ã ã‘. - this.queueService.userWebhookDeliver(merged, params.type, contents, { attempts: 1 }); + this.queueService.userWebhookDeliver(merged, type, contents, { attempts: 1 }); }; const dummyNote1 = generateDummyNote({ @@ -362,33 +376,45 @@ export class WebhookTestService { switch (params.type) { case 'note': { - send(toPackedNote(dummyNote1)); + send('note', { note: toPackedNote(dummyNote1) }); break; } case 'reply': { - send(toPackedNote(dummyReply1)); + send('reply', { note: toPackedNote(dummyReply1) }); break; } case 'renote': { - send(toPackedNote(dummyRenote1)); + send('renote', { note: toPackedNote(dummyRenote1) }); break; } case 'mention': { - send(toPackedNote(dummyMention1)); + send('mention', { note: toPackedNote(dummyMention1) }); + break; + } + case 'edited': { + send('edited', { note: toPackedNote(dummyNote1) }); break; } case 'follow': { - send(toPackedUserDetailedNotMe(dummyUser1)); + send('follow', { user: toPackedUserDetailedNotMe(dummyUser1) }); break; } case 'followed': { - send(toPackedUserLite(dummyUser2)); + send('followed', { user: toPackedUserLite(dummyUser2) }); break; } case 'unfollow': { - send(toPackedUserDetailedNotMe(dummyUser3)); + send('unfollow', { user: toPackedUserDetailedNotMe(dummyUser3) }); break; } + // ã¾ã 実装ã•れã¦ã„ãªã„ (#9485) + case 'reaction': + return; + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _exhaustiveAssertion: never = params.type; + return; + } } } @@ -401,10 +427,10 @@ export class WebhookTestService { * - é€ä¿¡å¯¾è±¡ã‚¤ãƒ™ãƒ³ãƒˆï¼ˆon)ã«é–¢ã™ã‚‹è¨å®š */ @bindThis - public async testSystemWebhook( + public async testSystemWebhook<T extends SystemWebhookEventType>( params: { webhookId: MiSystemWebhook['id'], - type: SystemWebhookEventType, + type: T, override?: Partial<Omit<MiSystemWebhook, 'id'>>, }, ) { @@ -414,7 +440,7 @@ export class WebhookTestService { } const webhook = webhooks[0]; - const send = (contents: unknown) => { + const send = <U extends SystemWebhookEventType>(type: U, contents: SystemWebhookPayload<U>) => { const merged = { ...webhook, ...params.override, @@ -422,12 +448,12 @@ export class WebhookTestService { // テスト目的ãªã®ã§SystemWebhookServiceã®æ©Ÿèƒ½ã‚’経由ã›ãšç›´æŽ¥ã‚ューã«è¿½åŠ ã™ã‚‹ï¼ˆãƒã‚§ãƒƒã‚¯å‡¦ç†ãªã©ã‚’スã‚ップã™ã‚‹æ„図). // ã¾ãŸã€Jobã®è©¦è¡Œå›žæ•°ã‚‚1回ã ã‘. - this.queueService.systemWebhookDeliver(merged, params.type, contents, { attempts: 1 }); + this.queueService.systemWebhookDeliver(merged, type, contents, { attempts: 1 }); }; switch (params.type) { case 'abuseReport': { - send(generateAbuseReport({ + send('abuseReport', generateAbuseReport({ targetUserId: dummyUser1.id, targetUser: dummyUser1, reporterId: dummyUser2.id, @@ -436,7 +462,7 @@ export class WebhookTestService { break; } case 'abuseReportResolved': { - send(generateAbuseReport({ + send('abuseReportResolved', generateAbuseReport({ targetUserId: dummyUser1.id, targetUser: dummyUser1, reporterId: dummyUser2.id, @@ -448,9 +474,30 @@ export class WebhookTestService { break; } case 'userCreated': { - send(toPackedUserLite(dummyUser1)); + send('userCreated', toPackedUserLite(dummyUser1)); + break; + } + case 'inactiveModeratorsWarning': { + const dummyTime: ModeratorInactivityRemainingTime = { + time: 100000, + asDays: 1, + asHours: 24, + }; + + send('inactiveModeratorsWarning', { + remainingTime: dummyTime, + }); break; } + case 'inactiveModeratorsInvitationOnlyChanged': { + send('inactiveModeratorsInvitationOnlyChanged', {}); + break; + } + default: { + // eslint-disable-next-line @typescript-eslint/no-unused-vars + const _exhaustiveAssertion: never = params.type; + return; + } } } } diff --git a/packages/backend/src/core/activitypub/ApInboxService.ts b/packages/backend/src/core/activitypub/ApInboxService.ts index 75ba5e97b9..278c97f907 100644 --- a/packages/backend/src/core/activitypub/ApInboxService.ts +++ b/packages/backend/src/core/activitypub/ApInboxService.ts @@ -135,6 +135,7 @@ export class ApInboxService { if (actor.uri) { if (actor.lastFetchedAt == null || Date.now() - actor.lastFetchedAt.getTime() > 1000 * 60 * 60 * 24) { setImmediate(() => { + // åŒä¸€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æƒ…å ±ã‚’å†åº¦å‡¦ç†ã™ã‚‹ã®ã§ã€ä½¿ç”¨æ¸ˆã¿ã®resolverã‚’å†åˆ©ç”¨ã—ã¦ã¯ã„ã‘ãªã„ this.apPersonService.updatePerson(actor.uri); }); } @@ -572,7 +573,7 @@ export class ApInboxService { @bindThis private async flag(actor: MiRemoteUser, activity: IFlag): Promise<string> { // Make sure the source instance is allowed to send reports. - const instance = await this.federatedInstanceService.fetch(actor.host); + const instance = await this.federatedInstanceService.fetchOrRegister(actor.host); if (instance.rejectReports) { throw new Bull.UnrecoverableError(`Rejecting report from instance: ${actor.host}`); } diff --git a/packages/backend/src/core/activitypub/ApRendererService.ts b/packages/backend/src/core/activitypub/ApRendererService.ts index 57489c754f..fb706a775f 100644 --- a/packages/backend/src/core/activitypub/ApRendererService.ts +++ b/packages/backend/src/core/activitypub/ApRendererService.ts @@ -520,6 +520,9 @@ export class ApRendererService { summary: profile.description ? this.mfmService.toHtml(mfm.parse(profile.description)) : null, _misskey_summary: profile.description, _misskey_followedMessage: profile.followedMessage, + _misskey_requireSigninToViewContents: user.requireSigninToViewContents, + _misskey_makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore, + _misskey_makeNotesHiddenBefore: user.makeNotesHiddenBefore, icon: avatar ? this.renderImage(avatar) : null, image: banner ? this.renderImage(banner) : null, backgroundUrl: background ? this.renderImage(background) : null, diff --git a/packages/backend/src/core/activitypub/ApRequestService.ts b/packages/backend/src/core/activitypub/ApRequestService.ts index 0dea615cb0..8036c9638f 100644 --- a/packages/backend/src/core/activitypub/ApRequestService.ts +++ b/packages/backend/src/core/activitypub/ApRequestService.ts @@ -11,14 +11,14 @@ import { DI } from '@/di-symbols.js'; import type { Config } from '@/config.js'; import type { MiUser } from '@/models/User.js'; import { UserKeypairService } from '@/core/UserKeypairService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { HttpRequestService } from '@/core/HttpRequestService.js'; import { LoggerService } from '@/core/LoggerService.js'; import { bindThis } from '@/decorators.js'; import type Logger from '@/logger.js'; -import type { IObject } from './type.js'; import { validateContentTypeSetAsActivityPub } from '@/core/activitypub/misc/validator.js'; import { assertActivityMatchesUrls } from '@/core/activitypub/misc/check-against-url.js'; -import { UtilityService } from "@/core/UtilityService.js"; +import type { IObject } from './type.js'; type Request = { url: string; diff --git a/packages/backend/src/core/activitypub/misc/contexts.ts b/packages/backend/src/core/activitypub/misc/contexts.ts index 9c2640758f..d7b6fc6589 100644 --- a/packages/backend/src/core/activitypub/misc/contexts.ts +++ b/packages/backend/src/core/activitypub/misc/contexts.ts @@ -558,6 +558,9 @@ const extension_context_definition = { '_misskey_votes': 'misskey:_misskey_votes', '_misskey_summary': 'misskey:_misskey_summary', '_misskey_followedMessage': 'misskey:_misskey_followedMessage', + '_misskey_requireSigninToViewContents': 'misskey:_misskey_requireSigninToViewContents', + '_misskey_makeNotesFollowersOnlyBefore': 'misskey:_misskey_makeNotesFollowersOnlyBefore', + '_misskey_makeNotesHiddenBefore': 'misskey:_misskey_makeNotesHiddenBefore', 'isCat': 'misskey:isCat', // Firefish firefish: 'https://joinfirefish.org/ns#', diff --git a/packages/backend/src/core/activitypub/models/ApImageService.ts b/packages/backend/src/core/activitypub/models/ApImageService.ts index c5c3b736d4..423044b985 100644 --- a/packages/backend/src/core/activitypub/models/ApImageService.ts +++ b/packages/backend/src/core/activitypub/models/ApImageService.ts @@ -74,7 +74,7 @@ export class ApImageService { // 2. or the image is not sensitive const shouldBeCached = this.meta.cacheRemoteFiles && (this.meta.cacheRemoteSensitiveFiles || !image.sensitive); - await this.federatedInstanceService.fetch(actor.host).then(async i => { + await this.federatedInstanceService.fetchOrRegister(actor.host).then(async i => { if (i.isNSFW) { image.sensitive = true; } diff --git a/packages/backend/src/core/activitypub/models/ApNoteService.ts b/packages/backend/src/core/activitypub/models/ApNoteService.ts index b8aa67e9ea..e4c4fe54b5 100644 --- a/packages/backend/src/core/activitypub/models/ApNoteService.ts +++ b/packages/backend/src/core/activitypub/models/ApNoteService.ts @@ -109,6 +109,10 @@ export class ApNoteService { return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', `invalid Note: attributedTo has different host. expected: ${expectHost}, actual: ${actualHost}`); } + if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) { + return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed'); + } + if (actor) { const attribution = (object.attributedTo) ? getOneApId(object.attributedTo) : actor.uri; if (attribution !== actor.uri) { @@ -119,10 +123,6 @@ export class ApNoteService { } } - if (object.published && !this.idService.isSafeT(new Date(object.published).valueOf())) { - return new IdentifiableError('d450b8a9-48e4-4dab-ae36-f4db763fda7c', 'invalid Note: published timestamp is malformed'); - } - if (note) { const url = (object.url) ? getOneApId(object.url) : note.url; if (url && url !== note.url) { @@ -411,7 +411,7 @@ export class ApNoteService { const object = await resolver.resolve(value); const entryUri = getApId(value); - const err = this.validateNote(object, entryUri); + const err = this.validateNote(object, entryUri, actor, user, updatedNote); if (err) { this.logger.error(err.message, { resolver: { history: resolver.getHistory() }, diff --git a/packages/backend/src/core/activitypub/models/ApPersonService.ts b/packages/backend/src/core/activitypub/models/ApPersonService.ts index b1bd379861..5c71dbc626 100644 --- a/packages/backend/src/core/activitypub/models/ApPersonService.ts +++ b/packages/backend/src/core/activitypub/models/ApPersonService.ts @@ -255,6 +255,12 @@ export class ApPersonService implements OnModuleInit { if (user == null) throw new Error('failed to create user: user is null'); const [avatar, banner, background] = await Promise.all([icon, image, bgimg].map(img => { + // icon and image may be arrays + // see https://www.w3.org/TR/activitystreams-vocabulary/#dfn-icon + if (Array.isArray(img)) { + img = img.find(item => item && item.url) ?? null; + } + // if we have an explicitly missing image, return an // explicitly-null set of values if ((img == null) || (typeof img === 'object' && img.url == null)) { @@ -398,7 +404,7 @@ export class ApPersonService implements OnModuleInit { usernameLower: person.preferredUsername?.toLowerCase(), host, inbox: person.inbox, - sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox, + sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null, notesCount: outboxcollection?.totalItems ?? 0, followersCount: followerscollection?.totalItems ?? 0, followingCount: followingcollection?.totalItems ?? 0, @@ -409,6 +415,9 @@ export class ApPersonService implements OnModuleInit { isBot, isCat: (person as any).isCat === true, speakAsCat: (person as any).speakAsCat != null ? (person as any).speakAsCat === true : (person as any).isCat === true, + requireSigninToViewContents: (person as any).requireSigninToViewContents === true, + makeNotesFollowersOnlyBefore: (person as any).makeNotesFollowersOnlyBefore ?? null, + makeNotesHiddenBefore: (person as any).makeNotesHiddenBefore ?? null, emojis, })) as MiRemoteUser; @@ -462,13 +471,15 @@ export class ApPersonService implements OnModuleInit { this.cacheService.uriPersonCache.set(user.uri, user); // Register host - this.federatedInstanceService.fetch(host).then(i => { - this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); - this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - if (this.meta.enableChartsForFederatedInstances) { - this.instanceChart.newUser(i.host); - } - }); + if (this.meta.enableStatsForFederatedInstances) { + this.federatedInstanceService.fetchOrRegister(host).then(i => { + this.instancesRepository.increment({ id: i.id }, 'usersCount', 1); + if (this.meta.enableChartsForFederatedInstances) { + this.instanceChart.newUser(i.host); + } + this.fetchInstanceMetadataService.fetchInstanceMetadata(i); + }); + } this.usersChart.update(user, true); @@ -574,7 +585,7 @@ export class ApPersonService implements OnModuleInit { const updates = { lastFetchedAt: new Date(), inbox: person.inbox, - sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox, + sharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null, followersUri: person.followers ? getApId(person.followers) : undefined, featured: person.featured, emojis: emojiNames, @@ -653,7 +664,7 @@ export class ApPersonService implements OnModuleInit { // è©²å½“ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒæ—¢ã«ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã«ãªã£ã¦ã„ãŸå ´åˆã¯Followingもアップデートã™ã‚‹ await this.followingsRepository.update( { followerId: exist.id }, - { followerSharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox }, + { followerSharedInbox: person.sharedInbox ?? person.endpoints?.sharedInbox ?? null }, ); await this.updateFeatured(exist.id, resolver).catch(err => this.logger.error(err)); diff --git a/packages/backend/src/core/activitypub/models/ApQuestionService.ts b/packages/backend/src/core/activitypub/models/ApQuestionService.ts index 79a4f49d92..335ca189ec 100644 --- a/packages/backend/src/core/activitypub/models/ApQuestionService.ts +++ b/packages/backend/src/core/activitypub/models/ApQuestionService.ts @@ -12,8 +12,8 @@ import type { IPoll } from '@/models/Poll.js'; import type { MiRemoteUser } from '@/models/User.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; -import { UtilityService } from '@/core/UtilityService.js'; import { getApId, getApType, getNullableApId, getOneApId, isQuestion } from '../type.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { ApLoggerService } from '../ApLoggerService.js'; import { ApResolverService } from '../ApResolverService.js'; import type { Resolver } from '../ApResolverService.js'; diff --git a/packages/backend/src/core/activitypub/type.ts b/packages/backend/src/core/activitypub/type.ts index 6da382e3ec..d67f8cf62e 100644 --- a/packages/backend/src/core/activitypub/type.ts +++ b/packages/backend/src/core/activitypub/type.ts @@ -17,6 +17,9 @@ export interface IObject { summary?: string | null; _misskey_summary?: string; _misskey_followedMessage?: string | null; + _misskey_requireSigninToViewContents?: boolean; + _misskey_makeNotesFollowersOnlyBefore?: number | null; + _misskey_makeNotesHiddenBefore?: number | null; published?: string; cc?: ApObject; to?: ApObject; diff --git a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts index a13c244c19..70ead890ab 100644 --- a/packages/backend/src/core/entities/AbuseUserReportEntityService.ts +++ b/packages/backend/src/core/entities/AbuseUserReportEntityService.ts @@ -53,6 +53,8 @@ export class AbuseUserReportEntityService { schema: 'UserDetailedNotMe', }) : null, forwarded: report.forwarded, + resolvedAs: report.resolvedAs, + moderationNote: report.moderationNote, }); } diff --git a/packages/backend/src/core/entities/FlashEntityService.ts b/packages/backend/src/core/entities/FlashEntityService.ts index 4aa7104c1e..7b0150f5b6 100644 --- a/packages/backend/src/core/entities/FlashEntityService.ts +++ b/packages/backend/src/core/entities/FlashEntityService.ts @@ -5,10 +5,8 @@ import { Inject, Injectable } from '@nestjs/common'; import { DI } from '@/di-symbols.js'; -import type { FlashsRepository, FlashLikesRepository } from '@/models/_.js'; -import { awaitAll } from '@/misc/prelude/await-all.js'; +import type { FlashLikesRepository, FlashsRepository } from '@/models/_.js'; import type { Packed } from '@/misc/json-schema.js'; -import type { } from '@/models/Blocking.js'; import type { MiUser } from '@/models/User.js'; import type { MiFlash } from '@/models/Flash.js'; import { bindThis } from '@/decorators.js'; @@ -20,10 +18,8 @@ export class FlashEntityService { constructor( @Inject(DI.flashsRepository) private flashsRepository: FlashsRepository, - @Inject(DI.flashLikesRepository) private flashLikesRepository: FlashLikesRepository, - private userEntityService: UserEntityService, private idService: IdService, ) { @@ -34,25 +30,36 @@ export class FlashEntityService { src: MiFlash['id'] | MiFlash, me?: { id: MiUser['id'] } | null | undefined, hint?: { - packedUser?: Packed<'UserLite'> + packedUser?: Packed<'UserLite'>, + likedFlashIds?: MiFlash['id'][], }, ): Promise<Packed<'Flash'>> { const meId = me ? me.id : null; const flash = typeof src === 'object' ? src : await this.flashsRepository.findOneByOrFail({ id: src }); - return await awaitAll({ + // { schema: 'UserDetailed' } ã™ã‚‹ã¨ç„¡é™ãƒ«ãƒ¼ãƒ—ã™ã‚‹ã®ã§æ³¨æ„ + const user = hint?.packedUser ?? await this.userEntityService.pack(flash.user ?? flash.userId, me); + + let isLiked = undefined; + if (meId) { + isLiked = hint?.likedFlashIds + ? hint.likedFlashIds.includes(flash.id) + : await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }); + } + + return { id: flash.id, createdAt: this.idService.parse(flash.id).date.toISOString(), updatedAt: flash.updatedAt.toISOString(), userId: flash.userId, - user: hint?.packedUser ?? this.userEntityService.pack(flash.user ?? flash.userId, me), // { schema: 'UserDetailed' } ã™ã‚‹ã¨ç„¡é™ãƒ«ãƒ¼ãƒ—ã™ã‚‹ã®ã§æ³¨æ„ + user: user, title: flash.title, summary: flash.summary, script: flash.script, visibility: flash.visibility, likedCount: flash.likedCount, - isLiked: meId ? await this.flashLikesRepository.exists({ where: { flashId: flash.id, userId: meId } }) : undefined, - }); + isLiked: isLiked, + }; } @bindThis @@ -63,7 +70,19 @@ export class FlashEntityService { const _users = flashes.map(({ user, userId }) => user ?? userId); const _userMap = await this.userEntityService.packMany(_users, me) .then(users => new Map(users.map(u => [u.id, u]))); - return Promise.all(flashes.map(flash => this.pack(flash, me, { packedUser: _userMap.get(flash.userId) }))); + const _likedFlashIds = me + ? await this.flashLikesRepository.createQueryBuilder('flashLike') + .select('flashLike.flashId') + .where('flashLike.userId = :userId', { userId: me.id }) + .getRawMany<{ flashLike_flashId: string }>() + .then(likes => [...new Set(likes.map(like => like.flashLike_flashId))]) + : []; + return Promise.all( + flashes.map(flash => this.pack(flash, me, { + packedUser: _userMap.get(flash.userId), + likedFlashIds: _likedFlashIds, + })), + ); } } diff --git a/packages/backend/src/core/entities/MetaEntityService.ts b/packages/backend/src/core/entities/MetaEntityService.ts index b2b9aebb79..7d7b4cbd81 100644 --- a/packages/backend/src/core/entities/MetaEntityService.ts +++ b/packages/backend/src/core/entities/MetaEntityService.ts @@ -100,6 +100,7 @@ export class MetaEntityService { turnstileSiteKey: instance.turnstileSiteKey, enableFC: instance.enableFC, fcSiteKey: instance.fcSiteKey, + enableTestcaptcha: instance.enableTestcaptcha, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl ?? '/assets/ai.png', diff --git a/packages/backend/src/core/entities/NoteEntityService.ts b/packages/backend/src/core/entities/NoteEntityService.ts index 7327e5e125..eb6b353752 100644 --- a/packages/backend/src/core/entities/NoteEntityService.ts +++ b/packages/backend/src/core/entities/NoteEntityService.ts @@ -25,6 +25,30 @@ import type { UserEntityService } from './UserEntityService.js'; import type { DriveFileEntityService } from './DriveFileEntityService.js'; import type { Config } from '@/config.js'; +// is-renote.tsã¨ã‚ˆã—ãªã«ãƒªãƒ³ã‚¯ +function isPureRenote(note: MiNote): note is MiNote & { renoteId: MiNote['id']; renote: MiNote } { + return ( + note.renote != null && + note.reply == null && + note.text == null && + note.cw == null && + (note.fileIds == null || note.fileIds.length === 0) && + !note.hasPoll + ); +} + +function getAppearNoteIds(notes: MiNote[]): Set<string> { + const appearNoteIds = new Set<string>(); + for (const note of notes) { + if (isPureRenote(note)) { + appearNoteIds.add(note.renoteId); + } else { + appearNoteIds.add(note.id); + } + } + return appearNoteIds; +} + @Injectable() export class NoteEntityService implements OnModuleInit { private userEntityService: UserEntityService; @@ -86,52 +110,86 @@ export class NoteEntityService implements OnModuleInit { } @bindThis - private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null) { + private async hideNote(packedNote: Packed<'Note'>, meId: MiUser['id'] | null): Promise<void> { + // FIXME: ã“ã®visibility変更処ç†ãŒå½“関数ã«ã‚ã‚‹ã®ã¯è‹¥å¹²ä¸è‡ªç„¶ã‹ã‚‚ã—れãªã„(関数åã‚’ treatVisibility ã¨ã‹ã«å¤‰ãˆã‚‹æ‰‹ã‚‚ã‚ã‚‹) + if (packedNote.visibility === 'public' || packedNote.visibility === 'home') { + const followersOnlyBefore = packedNote.user.makeNotesFollowersOnlyBefore; + if ((followersOnlyBefore != null) + && ( + (followersOnlyBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (followersOnlyBefore * 1000))) + || (followersOnlyBefore > 0 && (new Date(packedNote.createdAt).getTime() < followersOnlyBefore * 1000)) + ) + ) { + packedNote.visibility = 'followers'; + } + } + + if (meId === packedNote.userId) return; + // TODO: isVisibleForMe を使ã†ã‚ˆã†ã«ã—ã¦ã‚‚良ã•ãã†(åž‹é•ã†ã‘ã©) let hide = false; - // visibility ㌠specified ã‹ã¤è‡ªåˆ†ãŒæŒ‡å®šã•れã¦ã„ãªã‹ã£ãŸã‚‰éžè¡¨ç¤º - if (packedNote.visibility === 'specified') { - if (meId == null) { + if (packedNote.user.requireSigninToViewContents && meId == null) { + hide = true; + } + + if (!hide) { + const hiddenBefore = packedNote.user.makeNotesHiddenBefore; + if ((hiddenBefore != null) + && ( + (hiddenBefore <= 0 && (Date.now() - new Date(packedNote.createdAt).getTime() > 0 - (hiddenBefore * 1000))) + || (hiddenBefore > 0 && (new Date(packedNote.createdAt).getTime() < hiddenBefore * 1000)) + ) + ) { hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else { - // 指定ã•れã¦ã„ã‚‹ã‹ã©ã†ã‹ - const specified = packedNote.visibleUserIds!.some((id: any) => meId === id); + } + } - if (specified) { + // visibility ㌠specified ã‹ã¤è‡ªåˆ†ãŒæŒ‡å®šã•れã¦ã„ãªã‹ã£ãŸã‚‰éžè¡¨ç¤º + if (!hide) { + if (packedNote.visibility === 'specified') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { hide = false; } else { - hide = true; + // 指定ã•れã¦ã„ã‚‹ã‹ã©ã†ã‹ + const specified = packedNote.visibleUserIds!.some(id => meId === id); + + if (!specified) { + hide = true; + } } } } // visibility ㌠followers ã‹ã¤è‡ªåˆ†ãŒæŠ•稿者ã®ãƒ•ã‚©ãƒãƒ¯ãƒ¼ã§ãªã‹ã£ãŸã‚‰éžè¡¨ç¤º - if (packedNote.visibility === 'followers') { - if (meId == null) { - hide = true; - } else if (meId === packedNote.userId) { - hide = false; - } else if (packedNote.reply && (meId === packedNote.reply.userId)) { - // è‡ªåˆ†ã®æŠ•ç¨¿ã«å¯¾ã™ã‚‹ãƒªãƒ—ライ - hide = false; - } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { - // 自分ã¸ã®ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ - hide = false; - } else if (packedNote.renote && (meId === packedNote.renote.userId)) { - hide = false; - } else { - // フォãƒãƒ¯ãƒ¼ã‹ã©ã†ã‹ - const isFollowing = await this.followingsRepository.exists({ - where: { - followeeId: packedNote.userId, - followerId: meId, - }, - }); + if (!hide) { + if (packedNote.visibility === 'followers') { + if (meId == null) { + hide = true; + } else if (meId === packedNote.userId) { + hide = false; + } else if (packedNote.reply && (meId === packedNote.reply.userId)) { + // è‡ªåˆ†ã®æŠ•ç¨¿ã«å¯¾ã™ã‚‹ãƒªãƒ—ライ + hide = false; + } else if (packedNote.mentions && packedNote.mentions.some(id => meId === id)) { + // 自分ã¸ã®ãƒ¡ãƒ³ã‚·ãƒ§ãƒ³ + hide = false; + } else if (packedNote.renote && (meId === packedNote.renote.userId)) { + hide = false; + } else { + // フォãƒãƒ¯ãƒ¼ã‹ã©ã†ã‹ + // TODO: 当関数呼ã³å‡ºã—ã”ã¨ã«ã‚¯ã‚¨ãƒªãŒèµ°ã‚‹ã®ã¯é‡ãã†ã ã‹ã‚‰ãªã‚“ã¨ã‹ã™ã‚‹ + const isFollowing = await this.followingsRepository.exists({ + where: { + followeeId: packedNote.userId, + followerId: meId, + }, + }); - hide = !isFollowing; + hide = !isFollowing; + } } } @@ -163,6 +221,7 @@ export class NoteEntityService implements OnModuleInit { packedNote.reactionEmojis = {}; packedNote.reactions = {}; packedNote.isHidden = true; + // TODO: hiddenReason ã¿ãŸã„ãªã®ã‚’æä¾›ã—ã¦ã‚‚良ã•ãㆠ} } @@ -257,7 +316,7 @@ export class NoteEntityService implements OnModuleInit { return true; } else { // 指定ã•れã¦ã„ã‚‹ã‹ã©ã†ã‹ - return note.visibleUserIds.some((id: any) => meId === id); + return note.visibleUserIds.some(id => meId === id); } } @@ -459,7 +518,7 @@ export class NoteEntityService implements OnModuleInit { ) { if (notes.length === 0) return []; - const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany(notes.map(x => x.id)) : null; + const bufferedReactions = this.meta.enableReactionsBuffering ? await this.reactionsBufferingService.getMany([...getAppearNoteIds(notes)]) : null; const meId = me ? me.id : null; const myReactionsMap = new Map<MiNote['id'], string | null>(); @@ -470,7 +529,7 @@ export class NoteEntityService implements OnModuleInit { const oldId = this.idService.gen(Date.now() - 2000); for (const note of notes) { - if (note.renote && (note.text == null && note.fileIds.length === 0)) { // pure renote + if (isPureRenote(note)) { const reactionsCount = Object.values(this.reactionsBufferingService.mergeReactions(note.renote.reactions, bufferedReactions?.get(note.renote.id)?.deltas ?? {})).reduce((a, b) => a + b, 0); if (reactionsCount === 0) { myReactionsMap.set(note.renote.id, null); diff --git a/packages/backend/src/core/entities/UserEntityService.ts b/packages/backend/src/core/entities/UserEntityService.ts index 8a48c8c6b4..6bfe865038 100644 --- a/packages/backend/src/core/entities/UserEntityService.ts +++ b/packages/backend/src/core/entities/UserEntityService.ts @@ -543,6 +543,9 @@ export class UserEntityService implements OnModuleInit { isSilenced: user.isSilenced || this.roleService.getUserPolicies(user.id).then(r => !r.canPublicNote), speakAsCat: user.speakAsCat ?? false, approved: user.approved, + requireSigninToViewContents: user.requireSigninToViewContents === false ? undefined : true, + makeNotesFollowersOnlyBefore: user.makeNotesFollowersOnlyBefore ?? undefined, + makeNotesHiddenBefore: user.makeNotesHiddenBefore ?? undefined, instance: user.host ? this.federatedInstanceService.federatedInstanceCache.fetch(user.host).then(instance => instance ? { name: instance.name, softwareName: instance.softwareName, @@ -598,11 +601,6 @@ export class UserEntityService implements OnModuleInit { publicReactions: this.isLocalUser(user) ? profile!.publicReactions : false, // https://github.com/misskey-dev/misskey/issues/12964 followersVisibility: profile!.followersVisibility, followingVisibility: profile!.followingVisibility, - twoFactorEnabled: profile!.twoFactorEnabled, - usePasswordLessLogin: profile!.usePasswordLessLogin, - securityKeys: profile!.twoFactorEnabled - ? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1) - : false, roles: this.roleService.getUserRoles(user.id).then(roles => roles.filter(role => role.isPublic).sort((a, b) => b.displayOrder - a.displayOrder).map(role => ({ id: role.id, name: role.name, @@ -617,6 +615,14 @@ export class UserEntityService implements OnModuleInit { moderationNote: iAmModerator ? (profile!.moderationNote ?? '') : undefined, } : {}), + ...(isDetailed && (isMe || iAmModerator) ? { + twoFactorEnabled: profile!.twoFactorEnabled, + usePasswordLessLogin: profile!.usePasswordLessLogin, + securityKeys: profile!.twoFactorEnabled + ? this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1) + : false, + } : {}), + ...(isDetailed && isMe ? { avatarId: user.avatarId, bannerId: user.bannerId, diff --git a/packages/backend/src/misc/is-renote.ts b/packages/backend/src/misc/is-renote.ts index 99c755e38f..d6872de46a 100644 --- a/packages/backend/src/misc/is-renote.ts +++ b/packages/backend/src/misc/is-renote.ts @@ -6,6 +6,8 @@ import type { MiNote } from '@/models/Note.js'; import type { Packed } from '@/misc/json-schema.js'; +// NoteEntityService.isPureRenote ã¨ã‚ˆã—ãªã«ãƒªãƒ³ã‚¯ + type Renote = MiNote & { renoteId: NonNullable<MiNote['renoteId']> diff --git a/packages/backend/src/misc/sql-like-escape.ts b/packages/backend/src/misc/sql-like-escape.ts index ffe61670ee..6b4f51b00e 100644 --- a/packages/backend/src/misc/sql-like-escape.ts +++ b/packages/backend/src/misc/sql-like-escape.ts @@ -4,5 +4,5 @@ */ export function sqlLikeEscape(s: string) { - return s.replace(/([%_\\])/g, '\\$1'); + return s.replace(/([\\%_])/g, '\\$1'); } diff --git a/packages/backend/src/models/AbuseUserReport.ts b/packages/backend/src/models/AbuseUserReport.ts index 0615fd7eb5..d43ebf9342 100644 --- a/packages/backend/src/models/AbuseUserReport.ts +++ b/packages/backend/src/models/AbuseUserReport.ts @@ -7,6 +7,8 @@ import { PrimaryColumn, Entity, Index, JoinColumn, Column, ManyToOne } from 'typ import { id } from './util/id.js'; import { MiUser } from './User.js'; +export type AbuseReportResolveType = 'accept' | 'reject'; + @Entity('abuse_user_report') export class MiAbuseUserReport { @PrimaryColumn(id()) @@ -50,6 +52,9 @@ export class MiAbuseUserReport { }) public resolved: boolean; + /** + * リモートサーãƒãƒ¼ã«è»¢é€ã—ãŸã‹ã©ã†ã‹ + */ @Column('boolean', { default: false, }) @@ -60,6 +65,21 @@ export class MiAbuseUserReport { }) public comment: string; + @Column('varchar', { + length: 8192, default: '', + }) + public moderationNote: string; + + /** + * accept æ˜¯èª ... é€šå ±å†…å®¹ãŒæ£å½“ã§ã‚りã€è‚¯å®šçš„ã«å¯¾å¿œã•れ㟠+ * reject å¦èª ... é€šå ±å†…å®¹ãŒæ£å½“ã§ãªãã€å¦å®šçš„ã«å¯¾å¿œã•れ㟠+ * null ... ãã®ä»– + */ + @Column('varchar', { + length: 128, nullable: true, + }) + public resolvedAs: AbuseReportResolveType | null; + //#region Denormalized fields @Index() @Column('varchar', { diff --git a/packages/backend/src/models/Flash.ts b/packages/backend/src/models/Flash.ts index a1469a0d94..5db7dca992 100644 --- a/packages/backend/src/models/Flash.ts +++ b/packages/backend/src/models/Flash.ts @@ -7,6 +7,9 @@ import { Entity, Index, JoinColumn, Column, PrimaryColumn, ManyToOne } from 'typ import { id } from './util/id.js'; import { MiUser } from './User.js'; +export const flashVisibility = ['public', 'private'] as const; +export type FlashVisibility = typeof flashVisibility[number]; + @Entity('flash') export class MiFlash { @PrimaryColumn(id()) @@ -63,5 +66,5 @@ export class MiFlash { @Column('varchar', { length: 512, default: 'public', }) - public visibility: 'public' | 'private'; + public visibility: FlashVisibility; } diff --git a/packages/backend/src/models/Meta.ts b/packages/backend/src/models/Meta.ts index 0ea6765d6a..3fc3f273dd 100644 --- a/packages/backend/src/models/Meta.ts +++ b/packages/backend/src/models/Meta.ts @@ -84,6 +84,11 @@ export class MiMeta { @Column('varchar', { length: 1024, array: true, default: '{}', }) + public prohibitedWordsForNameOfUser: string[]; + + @Column('varchar', { + length: 1024, array: true, default: '{}', + }) public silencedHosts: string[]; @Column('varchar', { @@ -286,6 +291,11 @@ export class MiMeta { }) public fcSecretKey: string | null; + @Column('boolean', { + default: false, + }) + public enableTestcaptcha: boolean; + // chaptchaç³»ã‚’è¿½åŠ ã—ãŸéš›ã«ã¯nodeinfoã®ãƒ¬ã‚¹ãƒãƒ³ã‚¹ã«è¿½åŠ ã™ã‚‹ã®ã‚’忘れãªã„よã†ã«ã™ã‚‹ã“㨠@Column('enum', { @@ -570,6 +580,11 @@ export class MiMeta { public enableChartsForFederatedInstances: boolean; @Column('boolean', { + default: true, + }) + public enableStatsForFederatedInstances: boolean; + + @Column('boolean', { default: false, }) public enableServerMachineStats: boolean; diff --git a/packages/backend/src/models/Notification.ts b/packages/backend/src/models/Notification.ts index 53003a0a5a..6d7a453879 100644 --- a/packages/backend/src/models/Notification.ts +++ b/packages/backend/src/models/Notification.ts @@ -3,12 +3,12 @@ * SPDX-License-Identifier: AGPL-3.0-only */ +import { userExportableEntities } from '@/types.js'; import { MiUser } from './User.js'; import { MiNote } from './Note.js'; import { MiAccessToken } from './AccessToken.js'; import { MiRole } from './Role.js'; import { MiDriveFile } from './DriveFile.js'; -import { userExportableEntities } from '@/types.js'; export type MiNotification = { type: 'note'; @@ -87,6 +87,10 @@ export type MiNotification = { exportedEntity: typeof userExportableEntities[number]; fileId: MiDriveFile['id']; } | { + type: 'login'; + id: string; + createdAt: string; +} | { type: 'app'; id: string; createdAt: string; diff --git a/packages/backend/src/models/SystemWebhook.ts b/packages/backend/src/models/SystemWebhook.ts index d6c27eae51..1a7ce4962b 100644 --- a/packages/backend/src/models/SystemWebhook.ts +++ b/packages/backend/src/models/SystemWebhook.ts @@ -14,6 +14,10 @@ export const systemWebhookEventTypes = [ 'abuseReportResolved', // ユーザãŒä½œæˆã•ã‚ŒãŸæ™‚ 'userCreated', + // モデレータãŒä¸€å®šæœŸé–“ä¸åœ¨ã§ã‚ã‚‹è¦å‘Š + 'inactiveModeratorsWarning', + // モデレータãŒä¸€å®šæœŸé–“ä¸åœ¨ã®ãŸã‚システムã«ã‚ˆã‚Šæ‹›å¾…制ã¸ã¨å¤‰æ›´ã•れ㟠+ 'inactiveModeratorsInvitationOnlyChanged', ] as const; export type SystemWebhookEventType = typeof systemWebhookEventTypes[number]; diff --git a/packages/backend/src/models/User.ts b/packages/backend/src/models/User.ts index 35477fe009..3a825d36a7 100644 --- a/packages/backend/src/models/User.ts +++ b/packages/backend/src/models/User.ts @@ -244,6 +244,23 @@ export class MiUser { }) public isHibernated: boolean; + @Column('boolean', { + default: false, + }) + public requireSigninToViewContents: boolean; + + // in sec, マイナスã§ç›¸å¯¾æ™‚é–“ + @Column('integer', { + nullable: true, + }) + public makeNotesFollowersOnlyBefore: number | null; + + // in sec, マイナスã§ç›¸å¯¾æ™‚é–“ + @Column('integer', { + nullable: true, + }) + public makeNotesHiddenBefore: number | null; + // アカウントãŒå‰Šé™¤ã•れãŸã‹ã©ã†ã‹ã®ãƒ•ラグã ãŒã€å®Œå…¨ã«å‰Šé™¤ã•れる際ã¯ç‰©ç†å‰Šé™¤ãªã®ã§å®Ÿè³ªå‰Šé™¤ã•れるã¾ã§ã®ã€Œå‰Šé™¤ãŒé€²è¡Œã—ã¦ã„ã‚‹ã‹ã©ã†ã‹ã€ã®ãƒ•ラグ @Column('boolean', { default: false, diff --git a/packages/backend/src/models/json-schema/meta.ts b/packages/backend/src/models/json-schema/meta.ts index decdbd5650..5179e5d51c 100644 --- a/packages/backend/src/models/json-schema/meta.ts +++ b/packages/backend/src/models/json-schema/meta.ts @@ -139,6 +139,10 @@ export const packedMetaLiteSchema = { type: 'boolean', optional: false, nullable: true, }, + enableTestcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, swPublickey: { type: 'string', optional: false, nullable: true, diff --git a/packages/backend/src/models/json-schema/notification.ts b/packages/backend/src/models/json-schema/notification.ts index 26498e3e9d..248234a674 100644 --- a/packages/backend/src/models/json-schema/notification.ts +++ b/packages/backend/src/models/json-schema/notification.ts @@ -329,6 +329,16 @@ export const packedNotificationSchema = { type: { type: 'string', optional: false, nullable: false, + enum: ['login'], + }, + }, + }, { + type: 'object', + properties: { + ...baseSchema.properties, + type: { + type: 'string', + optional: false, nullable: false, enum: ['app'], }, body: { diff --git a/packages/backend/src/models/json-schema/user.ts b/packages/backend/src/models/json-schema/user.ts index 12ed1f2009..f953008b3f 100644 --- a/packages/backend/src/models/json-schema/user.ts +++ b/packages/backend/src/models/json-schema/user.ts @@ -150,6 +150,18 @@ export const packedUserLiteSchema = { type: 'boolean', nullable: false, optional: false, }, + requireSigninToViewContents: { + type: 'boolean', + nullable: false, optional: true, + }, + makeNotesFollowersOnlyBefore: { + type: 'number', + nullable: true, optional: true, + }, + makeNotesHiddenBefore: { + type: 'number', + nullable: true, optional: true, + }, instance: { type: 'object', nullable: false, optional: true, @@ -396,21 +408,6 @@ export const packedUserDetailedNotMeOnlySchema = { nullable: false, optional: false, enum: ['public', 'followers', 'private'], }, - twoFactorEnabled: { - type: 'boolean', - nullable: false, optional: false, - default: false, - }, - usePasswordLessLogin: { - type: 'boolean', - nullable: false, optional: false, - default: false, - }, - securityKeys: { - type: 'boolean', - nullable: false, optional: false, - default: false, - }, roles: { type: 'array', nullable: false, optional: false, @@ -432,6 +429,18 @@ export const packedUserDetailedNotMeOnlySchema = { type: 'string', nullable: false, optional: true, }, + twoFactorEnabled: { + type: 'boolean', + nullable: false, optional: true, + }, + usePasswordLessLogin: { + type: 'boolean', + nullable: false, optional: true, + }, + securityKeys: { + type: 'boolean', + nullable: false, optional: true, + }, //#region relations isFollowing: { type: 'boolean', @@ -693,6 +702,21 @@ export const packedMeDetailedOnlySchema = { nullable: false, optional: false, ref: 'RolePolicies', }, + twoFactorEnabled: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + usePasswordLessLogin: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, + securityKeys: { + type: 'boolean', + nullable: false, optional: false, + default: false, + }, //#region secrets email: { type: 'string', diff --git a/packages/backend/src/queue/QueueProcessorModule.ts b/packages/backend/src/queue/QueueProcessorModule.ts index dd588e0115..51fd97dc97 100644 --- a/packages/backend/src/queue/QueueProcessorModule.ts +++ b/packages/backend/src/queue/QueueProcessorModule.ts @@ -6,6 +6,7 @@ import { Module } from '@nestjs/common'; import { CoreModule } from '@/core/CoreModule.js'; import { GlobalModule } from '@/GlobalModule.js'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { QueueLoggerService } from './QueueLoggerService.js'; import { QueueProcessorService } from './QueueProcessorService.js'; import { DeliverProcessorService } from './processors/DeliverProcessorService.js'; @@ -85,6 +86,8 @@ import { ScheduleNotePostProcessorService } from './processors/ScheduleNotePostP DeliverProcessorService, InboxProcessorService, AggregateRetentionProcessorService, + CheckExpiredMutingsProcessorService, + CheckModeratorsActivityProcessorService, QueueProcessorService, ScheduleNotePostProcessorService, ], diff --git a/packages/backend/src/queue/QueueProcessorService.ts b/packages/backend/src/queue/QueueProcessorService.ts index 4cc5446062..33c2d02dd8 100644 --- a/packages/backend/src/queue/QueueProcessorService.ts +++ b/packages/backend/src/queue/QueueProcessorService.ts @@ -10,6 +10,7 @@ import type { Config } from '@/config.js'; import { DI } from '@/di-symbols.js'; import type Logger from '@/logger.js'; import { bindThis } from '@/decorators.js'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; import { StatusError } from '@/misc/status-error.js'; import { UserWebhookDeliverProcessorService } from './processors/UserWebhookDeliverProcessorService.js'; import { SystemWebhookDeliverProcessorService } from './processors/SystemWebhookDeliverProcessorService.js'; @@ -70,7 +71,7 @@ function getJobInfo(job: Bull.Job | undefined, increment = false): string { // onActiveã¨ã‹onCompletedã®attemptsMadeãŒãªãœã‹0å§‹ã¾ã‚Šãªã®ã§ã‚¤ãƒ³ã‚¯ãƒªãƒ¡ãƒ³ãƒˆã™ã‚‹ const currentAttempts = job.attemptsMade + (increment ? 1 : 0); - const maxAttempts = job.opts ? job.opts.attempts : 0; + const maxAttempts = job.opts.attempts ?? 0; return `id=${job.id} attempts=${currentAttempts}/${maxAttempts} age=${formated}`; } @@ -127,6 +128,7 @@ export class QueueProcessorService implements OnApplicationShutdown { private aggregateRetentionProcessorService: AggregateRetentionProcessorService, private checkExpiredMutingsProcessorService: CheckExpiredMutingsProcessorService, private bakeBufferedReactionsProcessorService: BakeBufferedReactionsProcessorService, + private checkModeratorsActivityProcessorService: CheckModeratorsActivityProcessorService, private cleanProcessorService: CleanProcessorService, private scheduleNotePostProcessorService: ScheduleNotePostProcessorService, ) { @@ -171,6 +173,7 @@ export class QueueProcessorService implements OnApplicationShutdown { case 'aggregateRetention': return this.aggregateRetentionProcessorService.process(); case 'checkExpiredMutings': return this.checkExpiredMutingsProcessorService.process(); case 'bakeBufferedReactions': return this.bakeBufferedReactionsProcessorService.process(); + case 'checkModeratorsActivity': return this.checkModeratorsActivityProcessorService.process(); case 'clean': return this.cleanProcessorService.process(); default: throw new Error(`unrecognized job type ${job.name} for system`); } diff --git a/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts new file mode 100644 index 0000000000..b81987cc15 --- /dev/null +++ b/packages/backend/src/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -0,0 +1,276 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { In } from 'typeorm'; +import type Logger from '@/logger.js'; +import { bindThis } from '@/decorators.js'; +import { MetaService } from '@/core/MetaService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { MiUser, type UserProfilesRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { AnnouncementService } from '@/core/AnnouncementService.js'; +import { QueueLoggerService } from '../QueueLoggerService.js'; + +// モデレーターãŒä¸åœ¨ã¨åˆ¤æ–ã™ã‚‹æ—¥ä»˜ã®é–¾å€¤ +const MODERATOR_INACTIVITY_LIMIT_DAYS = 7; +// è¦å‘Šé€šçŸ¥ã‚„ãƒã‚°å‡ºåŠ›ã‚’è¡Œã†æ®‹æ—¥æ•°ã®é–¾å€¤ +const MODERATOR_INACTIVITY_WARNING_REMAINING_DAYS = 2; +// 期é™ã‹ã‚‰6時間ã”ã¨ã«é€šçŸ¥ã‚’行ㆠ+const MODERATOR_INACTIVITY_WARNING_NOTIFY_INTERVAL_HOURS = 6; +const ONE_HOUR_MILLI_SEC = 1000 * 60 * 60; +const ONE_DAY_MILLI_SEC = ONE_HOUR_MILLI_SEC * 24; + +export type ModeratorInactivityEvaluationResult = { + isModeratorsInactive: boolean; + inactiveModerators: MiUser[]; + remainingTime: ModeratorInactivityRemainingTime; +} + +export type ModeratorInactivityRemainingTime = { + time: number; + asHours: number; + asDays: number; +}; + +function generateModeratorInactivityMail(remainingTime: ModeratorInactivityRemainingTime) { + const subject = 'Moderator Inactivity Warning'; + + const timeVariant = remainingTime.asDays === 0 ? `${remainingTime.asHours} hours` : `${remainingTime.asDays} days`; + const timeVariantJa = remainingTime.asDays === 0 ? `${remainingTime.asHours} 時間` : `${remainingTime.asDays} 日間`; + const message = [ + 'To Moderators,', + '', + `No moderator has been active for a period of time. After further ${timeVariant} of inactivity, the instance will switch to invitation only.`, + 'If you do not wish that to happen, please log into Sharkey to update your last active date and time.', + ]; + + const html = message.join('<br>'); + const text = message.join('\n'); + + return { + subject, + html, + text, + }; +} + +function generateInvitationOnlyChangedMail() { + const subject = 'Switch to invitation only'; + + const message = [ + 'To Moderators,', + '', + `The instance has been switched to invitation only, because no moderator activity was detected for ${MODERATOR_INACTIVITY_LIMIT_DAYS} days.`, + 'To change this, please log in and use the control panel.', + ]; + + const html = message.join('<br>'); + const text = message.join('\n'); + + return { + subject, + html, + text, + }; +} + +@Injectable() +export class CheckModeratorsActivityProcessorService { + private logger: Logger; + + constructor( + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + private metaService: MetaService, + private roleService: RoleService, + private emailService: EmailService, + private announcementService: AnnouncementService, + private systemWebhookService: SystemWebhookService, + private queueLoggerService: QueueLoggerService, + ) { + this.logger = this.queueLoggerService.logger.createSubLogger('check-moderators-activity'); + } + + @bindThis + public async process(): Promise<void> { + this.logger.info('start.'); + + const meta = await this.metaService.fetch(false); + if (!meta.disableRegistration) { + await this.processImpl(); + } else { + this.logger.info('is already invitation only.'); + } + + this.logger.succ('finish.'); + } + + @bindThis + private async processImpl() { + const evaluateResult = await this.evaluateModeratorsInactiveDays(); + if (evaluateResult.isModeratorsInactive) { + this.logger.warn(`The moderator has been inactive for ${MODERATOR_INACTIVITY_LIMIT_DAYS} days. We will move to invitation only.`); + + await this.changeToInvitationOnly(); + await this.notifyChangeToInvitationOnly(); + } else { + const remainingTime = evaluateResult.remainingTime; + if (remainingTime.asDays <= MODERATOR_INACTIVITY_WARNING_REMAINING_DAYS) { + const timeVariant = remainingTime.asDays === 0 ? `${remainingTime.asHours} hours` : `${remainingTime.asDays} days`; + this.logger.warn(`A moderator has been inactive for a period of time. If you are inactive for an additional ${timeVariant}, it will switch to invitation only.`); + + if (remainingTime.asHours % MODERATOR_INACTIVITY_WARNING_NOTIFY_INTERVAL_HOURS === 0) { + // ジョブã®å®Ÿè¡Œé »åº¦ã¨åŒç‰ã ã¨é€šçŸ¥ãŒå¤šã™ãŽã‚‹ãŸã‚期é™ã‹ã‚‰6時間ã”ã¨ã«é€šçŸ¥ã™ã‚‹ + // ã¤ã¾ã‚Šã€ã®ã“り2日を切ã£ãŸã‚‰6時間ã”ã¨ã«é€šçŸ¥ãŒé€ã‚‰ã‚Œã‚‹ + await this.notifyInactiveModeratorsWarning(remainingTime); + } + } + } + } + + /** + * モデレーターãŒä¸åœ¨ã§ã‚ã‚‹ã‹ã©ã†ã‹ã‚’確èªã™ã‚‹ã€‚trueã®å ´åˆã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒä¸åœ¨ã§ã‚る。 + * isModerator, isAdministrator, isRootã®ã„ãšã‚Œã‹ãŒtrueã®ãƒ¦ãƒ¼ã‚¶ã‚’対象ã«ã€ + * {@link MiUser.lastActiveDate}ã®å€¤ãŒå®Ÿè¡Œæ—¥æ™‚ã®{@link MODERATOR_INACTIVITY_LIMIT_DAYS}æ—¥å‰ã‚ˆã‚Šã‚‚å¤ã„ユーザãŒã„ã‚‹ã‹ã©ã†ã‹ã‚’確èªã™ã‚‹ã€‚ + * {@link MiUser.lastActiveDate}ãŒnullã®å ´åˆã¯ã€ãã®ãƒ¦ãƒ¼ã‚¶ã¯ç¢ºèªã®å¯¾è±¡å¤–ã¨ã™ã‚‹ã€‚ + * + * ----- + * + * ### サンプルパターン + * - 実行日時: 2022-01-30 12:00:00 + * - 判定基準: 2022-01-23 12:00:00(実行日時ã®{@link MODERATOR_INACTIVITY_LIMIT_DAYS}æ—¥å‰ï¼‰ + * + * #### パターン①+ * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト + * - モデレータB: lastActiveDate = 2022-01-23 12:00:00 ※セーフ(判定基準ã¨åŒå€¤ãªã®ã§ã‚®ãƒªã‚®ãƒªæ®‹ã‚Š0日) + * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日) + * - モデレータD: lastActiveDate = null + * + * ã“ã®å ´åˆã€ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿Bã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティã®ã¿åˆ¤å®šåŸºæº–日よりもå¤ããªã„ãŸã‚ã€ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒåœ¨å¸ã¨åˆ¤æ–ã•れる。 + * + * #### パターン② + * - モデレータA: lastActiveDate = 2022-01-20 00:00:00 ※アウト + * - モデレータB: lastActiveDate = 2022-01-22 12:00:00 ※アウト(残り-1日) + * - モデレータC: lastActiveDate = 2022-01-23 11:59:59 ※アウト(残り-1日) + * - モデレータD: lastActiveDate = null + * + * ã“ã®å ´åˆã€ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿A, B, Cã®ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ティã¯åˆ¤å®šåŸºæº–日よりもå¤ã„ãŸã‚ã€ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒä¸åœ¨ã¨åˆ¤æ–ã•れる。 + */ + @bindThis + public async evaluateModeratorsInactiveDays(): Promise<ModeratorInactivityEvaluationResult> { + const today = new Date(); + const inactivePeriod = new Date(today); + inactivePeriod.setDate(today.getDate() - MODERATOR_INACTIVITY_LIMIT_DAYS); + + const moderators = await this.fetchModerators() + .then(it => it.filter(it => it.lastActiveDate != null)); + const inactiveModerators = moderators + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + .filter(it => it.lastActiveDate!.getTime() < inactivePeriod.getTime()); + + // 残りã®çŒ¶äºˆã‚’示ã—ãŸã„ã®ã§ã€æœ€çµ‚アクティブ日時ãŒä¸€ç•ªè‹¥ã„ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ã®æ—¥æ•°ã‚’基準ã«çŒ¶äºˆã‚’計算ã™ã‚‹ + // eslint-disable-next-line @typescript-eslint/no-non-null-assertion + const newestLastActiveDate = new Date(Math.max(...moderators.map(it => it.lastActiveDate!.getTime()))); + const remainingTime = newestLastActiveDate.getTime() - inactivePeriod.getTime(); + const remainingTimeAsDays = Math.floor(remainingTime / ONE_DAY_MILLI_SEC); + const remainingTimeAsHours = Math.floor((remainingTime / ONE_HOUR_MILLI_SEC)); + + return { + isModeratorsInactive: inactiveModerators.length === moderators.length, + inactiveModerators, + remainingTime: { + time: remainingTime, + asHours: remainingTimeAsHours, + asDays: remainingTimeAsDays, + }, + }; + } + + @bindThis + private async changeToInvitationOnly() { + await this.metaService.update({ disableRegistration: true }); + } + + @bindThis + public async notifyInactiveModeratorsWarning(remainingTime: ModeratorInactivityRemainingTime) { + // -- モデレータã¸ã®ãƒ¡ãƒ¼ãƒ«é€ä¿¡ + + const moderators = await this.fetchModerators(); + const moderatorProfiles = await this.userProfilesRepository + .findBy({ userId: In(moderators.map(it => it.id)) }) + .then(it => new Map(it.map(it => [it.userId, it]))); + + const mail = generateModeratorInactivityMail(remainingTime); + for (const moderator of moderators) { + const profile = moderatorProfiles.get(moderator.id); + if (profile && profile.email && profile.emailVerified) { + this.emailService.sendEmail(profile.email, mail.subject, mail.html, mail.text); + } + } + + // -- SystemWebhook + + const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks() + .then(it => it.filter(it => it.on.includes('inactiveModeratorsWarning'))); + for (const systemWebhook of systemWebhooks) { + this.systemWebhookService.enqueueSystemWebhook( + systemWebhook, + 'inactiveModeratorsWarning', + { remainingTime: remainingTime }, + ); + } + } + + @bindThis + public async notifyChangeToInvitationOnly() { + // -- モデレータã¸ã®ãƒ¡ãƒ¼ãƒ«ã¨ãŠçŸ¥ã‚‰ã›ï¼ˆå€‹äººå‘ã‘)é€ä¿¡ + + const moderators = await this.fetchModerators(); + const moderatorProfiles = await this.userProfilesRepository + .findBy({ userId: In(moderators.map(it => it.id)) }) + .then(it => new Map(it.map(it => [it.userId, it]))); + + const mail = generateInvitationOnlyChangedMail(); + for (const moderator of moderators) { + this.announcementService.create({ + title: mail.subject, + text: mail.text, + forExistingUsers: true, + needConfirmationToRead: true, + userId: moderator.id, + }); + + const profile = moderatorProfiles.get(moderator.id); + if (profile && profile.email && profile.emailVerified) { + this.emailService.sendEmail(profile.email, mail.subject, mail.html, mail.text); + } + } + + // -- SystemWebhook + + const systemWebhooks = await this.systemWebhookService.fetchActiveSystemWebhooks() + .then(it => it.filter(it => it.on.includes('inactiveModeratorsInvitationOnlyChanged'))); + for (const systemWebhook of systemWebhooks) { + this.systemWebhookService.enqueueSystemWebhook( + systemWebhook, + 'inactiveModeratorsInvitationOnlyChanged', + {}, + ); + } + } + + @bindThis + private async fetchModerators() { + // TODO: モデレーター以外ã«ã‚‚ç‰¹åˆ¥ãªæ¨©é™ã‚’æŒã¤ãƒ¦ãƒ¼ã‚¶ãƒ¼ãŒã„ã‚‹å ´åˆã¯è€ƒæ…®ã™ã‚‹ + return this.roleService.getModerators({ + includeAdmins: true, + includeRoot: true, + excludeExpire: true, + }); + } +} diff --git a/packages/backend/src/queue/processors/DeliverProcessorService.ts b/packages/backend/src/queue/processors/DeliverProcessorService.ts index 9590a4fe71..5a16496011 100644 --- a/packages/backend/src/queue/processors/DeliverProcessorService.ts +++ b/packages/backend/src/queue/processors/DeliverProcessorService.ts @@ -74,8 +74,17 @@ export class DeliverProcessorService { try { await this.apRequestService.signedPost(job.data.user, job.data.to, job.data.content, job.data.digest); - // Update stats - this.federatedInstanceService.fetch(host).then(i => { + this.apRequestChart.deliverSucc(); + this.federationChart.deliverd(host, true); + + // Update instance stats + process.nextTick(async () => { + const i = await (this.meta.enableStatsForFederatedInstances + ? this.federatedInstanceService.fetchOrRegister(host) + : this.federatedInstanceService.fetch(host)); + + if (i == null) return; + if (i.isNotResponding) { this.federatedInstanceService.update(i.id, { isNotResponding: false, @@ -83,9 +92,9 @@ export class DeliverProcessorService { }); } - this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - this.apRequestChart.deliverSucc(); - this.federationChart.deliverd(i.host, true); + if (this.meta.enableStatsForFederatedInstances) { + this.fetchInstanceMetadataService.fetchInstanceMetadata(i); + } if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestSent(i.host, true); @@ -94,8 +103,11 @@ export class DeliverProcessorService { return 'Success'; } catch (res) { - // Update stats - this.federatedInstanceService.fetch(host).then(i => { + this.apRequestChart.deliverFail(); + this.federationChart.deliverd(host, false); + + // Update instance stats + this.federatedInstanceService.fetchOrRegister(host).then(i => { if (!i.isNotResponding) { this.federatedInstanceService.update(i.id, { isNotResponding: true, @@ -116,9 +128,6 @@ export class DeliverProcessorService { }); } - this.apRequestChart.deliverFail(); - this.federationChart.deliverd(i.host, false); - if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestSent(i.host, false); } @@ -129,7 +138,7 @@ export class DeliverProcessorService { if (!res.isRetryable) { // 相手ãŒé–‰éŽ–ã—ã¦ã„ã‚‹ã“ã¨ã‚’明示ã—ã¦ã„ã‚‹ãŸã‚ã€é…é€åœæ¢ã™ã‚‹ if (job.data.isSharedInbox && res.statusCode === 410) { - this.federatedInstanceService.fetch(host).then(i => { + this.federatedInstanceService.fetchOrRegister(host).then(i => { this.federatedInstanceService.update(i.id, { suspensionState: 'goneSuspended', }); diff --git a/packages/backend/src/queue/processors/InboxProcessorService.ts b/packages/backend/src/queue/processors/InboxProcessorService.ts index eaf2aa3c4c..7dfa4ec704 100644 --- a/packages/backend/src/queue/processors/InboxProcessorService.ts +++ b/packages/backend/src/queue/processors/InboxProcessorService.ts @@ -60,7 +60,7 @@ export class InboxProcessorService implements OnApplicationShutdown { private queueLoggerService: QueueLoggerService, ) { this.logger = this.queueLoggerService.logger.createSubLogger('inbox'); - this.updateInstanceQueue = new CollapsedQueue(60 * 1000 * 5, this.collapseUpdateInstanceJobs, this.performUpdateInstance); + this.updateInstanceQueue = new CollapsedQueue(process.env.NODE_ENV !== 'test' ? 60 * 1000 * 5 : 0, this.collapseUpdateInstanceJobs, this.performUpdateInstance); } @bindThis @@ -198,21 +198,27 @@ export class InboxProcessorService implements OnApplicationShutdown { delete activity.id; } - // Update stats - this.federatedInstanceService.fetch(authUser.user.host).then(i => { + this.apRequestChart.inbox(); + this.federationChart.inbox(authUser.user.host); + + // Update instance stats + process.nextTick(async () => { + const i = await (this.meta.enableStatsForFederatedInstances + ? this.federatedInstanceService.fetchOrRegister(authUser.user.host) + : this.federatedInstanceService.fetch(authUser.user.host)); + + if (i == null) return; + this.updateInstanceQueue.enqueue(i.id, { latestRequestReceivedAt: new Date(), shouldUnsuspend: i.suspensionState === 'autoSuspendedForNotResponding', }); - this.fetchInstanceMetadataService.fetchInstanceMetadata(i); - - this.apRequestChart.inbox(); - this.federationChart.inbox(i.host); - if (this.meta.enableChartsForFederatedInstances) { this.instanceChart.requestReceived(i.host); } + + this.fetchInstanceMetadataService.fetchInstanceMetadata(i); }); // ã‚¢ã‚¯ãƒ†ã‚£ãƒ“ãƒ†ã‚£ã‚’å‡¦ç† diff --git a/packages/backend/src/server/ActivityPubServerService.ts b/packages/backend/src/server/ActivityPubServerService.ts index f955329fd1..815bf278c7 100644 --- a/packages/backend/src/server/ActivityPubServerService.ts +++ b/packages/backend/src/server/ActivityPubServerService.ts @@ -33,6 +33,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { bindThis } from '@/decorators.js'; import { IActivity } from '@/core/activitypub/type.js'; import { isQuote, isRenote } from '@/misc/is-renote.js'; +import * as Acct from '@/misc/acct.js'; import type { FastifyInstance, FastifyRequest, FastifyReply, FastifyPluginOptions, FastifyBodyParser } from 'fastify'; import type { FindOptionsWhere } from 'typeorm'; import type Logger from '@/logger.js'; @@ -229,7 +230,7 @@ export class ActivityPubServerService { let signature; try { - signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'digest', 'host', 'date'], authorizationHeaderName: 'signature' }); + signature = httpSignature.parseRequest(request.raw, { 'headers': ['(request-target)', 'host', 'date'], authorizationHeaderName: 'signature' }); } catch (e) { reply.code(401); return; @@ -619,7 +620,18 @@ export class ActivityPubServerService { return; } + // リモートã ã£ãŸã‚‰ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆ + if (user.host != null) { + if (user.uri == null || this.utilityService.isSelfHost(user.host)) { + reply.code(500); + return; + } + reply.redirect(user.uri, 301); + return; + } + if (!this.config.checkActivityPubGetSignature) reply.header('Cache-Control', 'public, max-age=180'); + this.setResponseType(request, reply); return (this.apRendererService.addContext(await this.apRendererService.renderPerson(user as MiLocalUser))); } @@ -795,21 +807,22 @@ export class ActivityPubServerService { const user = await this.usersRepository.findOneBy({ id: userId, - host: IsNull(), isSuspended: false, }); return await this.userInfo(request, reply, user); }); - fastify.get<{ Params: { user: string; } }>('/@:user', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { - if (await this.shouldRefuseGetRequest(request, reply, request.params.user)) return; + fastify.get<{ Params: { acct: string; } }>('/@:acct', { constraints: { apOrHtml: 'ap' } }, async (request, reply) => { + if (await this.shouldRefuseGetRequest(request, reply, request.params.acct)) return; vary(reply.raw, 'Accept'); + const acct = Acct.parse(request.params.acct); + const user = await this.usersRepository.findOneBy({ - usernameLower: request.params.user.toLowerCase(), - host: IsNull(), + usernameLower: acct.username, + host: acct.host ?? IsNull(), isSuspended: false, }); diff --git a/packages/backend/src/server/api/ApiServerService.ts b/packages/backend/src/server/api/ApiServerService.ts index ac3b982742..0d77309537 100644 --- a/packages/backend/src/server/api/ApiServerService.ts +++ b/packages/backend/src/server/api/ApiServerService.ts @@ -119,25 +119,30 @@ export class ApiServerService { 'g-recaptcha-response'?: string; 'turnstile-response'?: string; 'frc-captcha-solution'?: string; + 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; } }>('/signup', (request, reply) => this.signupApiService.signup(request, reply)); fastify.post<{ Body: { username: string; - password: string; + password?: string; token?: string; - signature?: string; - authenticatorData?: string; - clientDataJSON?: string; - credentialId?: string; - challengeId?: string; + credential?: AuthenticationResponseJSON; + 'hcaptcha-response'?: string; + 'g-recaptcha-response'?: string; + 'turnstile-response'?: string; + 'frc-captcha-solution'?: string; + 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; }; - }>('/signin', (request, reply) => this.signinApiService.signin(request, reply)); + }>('/signin-flow', (request, reply) => this.signinApiService.signin(request, reply)); fastify.post<{ Body: { credential?: AuthenticationResponseJSON; + context?: string; }; }>('/signin-with-passkey', (request, reply) => this.signinWithPasskeyApiService.signin(request, reply)); diff --git a/packages/backend/src/server/api/EndpointsModule.ts b/packages/backend/src/server/api/EndpointsModule.ts index c478bebdaf..e319d6e0a4 100644 --- a/packages/backend/src/server/api/EndpointsModule.ts +++ b/packages/backend/src/server/api/EndpointsModule.ts @@ -68,6 +68,8 @@ import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; +import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js'; +import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js'; import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; @@ -473,6 +475,8 @@ const $admin_relays_list: Provider = { provide: 'ep:admin/relays/list', useClass const $admin_relays_remove: Provider = { provide: 'ep:admin/relays/remove', useClass: ep___admin_relays_remove.default }; const $admin_resetPassword: Provider = { provide: 'ep:admin/reset-password', useClass: ep___admin_resetPassword.default }; const $admin_resolveAbuseUserReport: Provider = { provide: 'ep:admin/resolve-abuse-user-report', useClass: ep___admin_resolveAbuseUserReport.default }; +const $admin_forwardAbuseUserReport: Provider = { provide: 'ep:admin/forward-abuse-user-report', useClass: ep___admin_forwardAbuseUserReport.default }; +const $admin_updateAbuseUserReport: Provider = { provide: 'ep:admin/update-abuse-user-report', useClass: ep___admin_updateAbuseUserReport.default }; const $admin_sendEmail: Provider = { provide: 'ep:admin/send-email', useClass: ep___admin_sendEmail.default }; const $admin_serverInfo: Provider = { provide: 'ep:admin/server-info', useClass: ep___admin_serverInfo.default }; const $admin_showModerationLogs: Provider = { provide: 'ep:admin/show-moderation-logs', useClass: ep___admin_showModerationLogs.default }; @@ -882,6 +886,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_relays_remove, $admin_resetPassword, $admin_resolveAbuseUserReport, + $admin_forwardAbuseUserReport, + $admin_updateAbuseUserReport, $admin_sendEmail, $admin_serverInfo, $admin_showModerationLogs, @@ -1285,6 +1291,8 @@ const $reversi_verify: Provider = { provide: 'ep:reversi/verify', useClass: ep__ $admin_relays_remove, $admin_resetPassword, $admin_resolveAbuseUserReport, + $admin_forwardAbuseUserReport, + $admin_updateAbuseUserReport, $admin_sendEmail, $admin_serverInfo, $admin_showModerationLogs, diff --git a/packages/backend/src/server/api/GetterService.ts b/packages/backend/src/server/api/GetterService.ts index 8643be0f30..419017aaf4 100644 --- a/packages/backend/src/server/api/GetterService.ts +++ b/packages/backend/src/server/api/GetterService.ts @@ -42,6 +42,17 @@ export class GetterService { return note; } + @bindThis + public async getNoteWithUser(noteId: MiNote['id']) { + const note = await this.notesRepository.findOne({ where: { id: noteId }, relations: ['user'] }); + + if (note == null) { + throw new IdentifiableError('9725d0ce-ba28-4dde-95a7-2cbb2c15de24', 'No such note.'); + } + + return note; + } + /** * Get note for API processing */ diff --git a/packages/backend/src/server/api/SigninApiService.ts b/packages/backend/src/server/api/SigninApiService.ts index 1a4ce0a54c..fa9155d82d 100644 --- a/packages/backend/src/server/api/SigninApiService.ts +++ b/packages/backend/src/server/api/SigninApiService.ts @@ -6,12 +6,14 @@ import { Inject, Injectable } from '@nestjs/common'; import bcrypt from 'bcryptjs'; import * as argon2 from 'argon2'; -import * as OTPAuth from 'otpauth'; import { IsNull } from 'typeorm'; +import * as Misskey from 'misskey-js'; import { DI } from '@/di-symbols.js'; import type { + MiMeta, SigninsRepository, UserProfilesRepository, + UserSecurityKeysRepository, UsersRepository, } from '@/models/_.js'; import type { Config } from '@/config.js'; @@ -21,8 +23,9 @@ import { IdService } from '@/core/IdService.js'; import { bindThis } from '@/decorators.js'; import { WebAuthnService } from '@/core/WebAuthnService.js'; import { UserAuthService } from '@/core/UserAuthService.js'; +import { CaptchaService } from '@/core/CaptchaService.js'; +import { FastifyReplyError } from '@/misc/fastify-reply-error.js'; import { isSystemAccount } from '@/misc/is-system-account.js'; -import type { MiMeta } from '@/models/_.js'; import { SkRateLimiterService } from '@/server/api/SkRateLimiterService.js'; import { sendRateLimitHeaders } from '@/misc/rate-limit-utils.js'; import { SigninService } from './SigninService.js'; @@ -44,6 +47,9 @@ export class SigninApiService { @Inject(DI.userProfilesRepository) private userProfilesRepository: UserProfilesRepository, + @Inject(DI.userSecurityKeysRepository) + private userSecurityKeysRepository: UserSecurityKeysRepository, + @Inject(DI.signinsRepository) private signinsRepository: SigninsRepository, @@ -52,6 +58,7 @@ export class SigninApiService { private signinService: SigninService, private userAuthService: UserAuthService, private webAuthnService: WebAuthnService, + private captchaService: CaptchaService, ) { } @@ -60,9 +67,15 @@ export class SigninApiService { request: FastifyRequest<{ Body: { username: string; - password: string; + password?: string; token?: string; credential?: AuthenticationResponseJSON; + 'hcaptcha-response'?: string; + 'g-recaptcha-response'?: string; + 'turnstile-response'?: string; + 'frc-captcha-solution'?: string; + 'm-captcha-response'?: string; + 'testcaptcha-response'?: string; }; }>, reply: FastifyReply, @@ -101,11 +114,6 @@ export class SigninApiService { return; } - if (typeof password !== 'string') { - reply.code(400); - return; - } - if (token != null && typeof token !== 'string') { reply.code(400); return; @@ -136,6 +144,27 @@ export class SigninApiService { } const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + const securityKeysAvailable = await this.userSecurityKeysRepository.countBy({ userId: user.id }).then(result => result >= 1); + + if (password == null) { + reply.code(200); + if (profile.twoFactorEnabled) { + return { + finished: false, + next: 'password', + } satisfies Misskey.entities.SigninFlowResponse; + } else { + return { + finished: false, + next: 'captcha', + } satisfies Misskey.entities.SigninFlowResponse; + } + } + + if (typeof password !== 'string') { + reply.code(400); + return; + } if (!user.approved && this.meta.approvalRequiredForSignup) { reply.code(403); @@ -151,7 +180,7 @@ export class SigninApiService { // Compare password const same = await argon2.verify(profile.password!, password) || bcrypt.compareSync(password, profile.password!); - const fail = async (status?: number, failure?: { id: string }) => { + const fail = async (status?: number, failure?: { id: string; }) => { // Append signin history await this.signinsRepository.insert({ id: this.idService.gen(), @@ -165,6 +194,44 @@ export class SigninApiService { }; if (!profile.twoFactorEnabled) { + if (process.env.NODE_ENV !== 'test') { + if (this.meta.enableHcaptcha && this.meta.hcaptchaSecretKey) { + await this.captchaService.verifyHcaptcha(this.meta.hcaptchaSecretKey, body['hcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + + if (this.meta.enableMcaptcha && this.meta.mcaptchaSecretKey && this.meta.mcaptchaSitekey && this.meta.mcaptchaInstanceUrl) { + await this.captchaService.verifyMcaptcha(this.meta.mcaptchaSecretKey, this.meta.mcaptchaSitekey, this.meta.mcaptchaInstanceUrl, body['m-captcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + + if (this.meta.enableFC && this.meta.fcSecretKey) { + await this.captchaService.verifyFriendlyCaptcha(this.meta.fcSecretKey, body['frc-captcha-solution']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + + if (this.meta.enableRecaptcha && this.meta.recaptchaSecretKey) { + await this.captchaService.verifyRecaptcha(this.meta.recaptchaSecretKey, body['g-recaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + + if (this.meta.enableTurnstile && this.meta.turnstileSecretKey) { + await this.captchaService.verifyTurnstile(this.meta.turnstileSecretKey, body['turnstile-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + + if (this.meta.enableTestcaptcha) { + await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } + } + if (same) { if (profile.password!.startsWith('$2')) { const newHash = await argon2.hash(password); @@ -223,7 +290,7 @@ export class SigninApiService { id: '93b86c4b-72f9-40eb-9815-798928603d1e', }); } - } else { + } else if (securityKeysAvailable) { if (!same && !profile.usePasswordLessLogin) { return await fail(403, { id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', @@ -233,7 +300,23 @@ export class SigninApiService { const authRequest = await this.webAuthnService.initiateAuthentication(user.id); reply.code(200); - return authRequest; + return { + finished: false, + next: 'passkey', + authRequest, + } satisfies Misskey.entities.SigninFlowResponse; + } else { + if (!same || !profile.twoFactorEnabled) { + return await fail(403, { + id: '932c904e-9460-45b7-9ce6-7ed33be7eb2c', + }); + } else { + reply.code(200); + return { + finished: false, + next: 'totp', + } satisfies Misskey.entities.SigninFlowResponse; + } } // never get here } diff --git a/packages/backend/src/server/api/SigninService.ts b/packages/backend/src/server/api/SigninService.ts index 70306c3113..640356b50c 100644 --- a/packages/backend/src/server/api/SigninService.ts +++ b/packages/backend/src/server/api/SigninService.ts @@ -4,13 +4,16 @@ */ import { Inject, Injectable } from '@nestjs/common'; +import * as Misskey from 'misskey-js'; import { DI } from '@/di-symbols.js'; -import type { SigninsRepository } from '@/models/_.js'; +import type { SigninsRepository, UserProfilesRepository } from '@/models/_.js'; import { IdService } from '@/core/IdService.js'; import type { MiLocalUser } from '@/models/User.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { SigninEntityService } from '@/core/entities/SigninEntityService.js'; import { bindThis } from '@/decorators.js'; +import { EmailService } from '@/core/EmailService.js'; +import { NotificationService } from '@/core/NotificationService.js'; import type { FastifyRequest, FastifyReply } from 'fastify'; @Injectable() @@ -19,7 +22,12 @@ export class SigninService { @Inject(DI.signinsRepository) private signinsRepository: SigninsRepository, + @Inject(DI.userProfilesRepository) + private userProfilesRepository: UserProfilesRepository, + private signinEntityService: SigninEntityService, + private emailService: EmailService, + private notificationService: NotificationService, private idService: IdService, private globalEventService: GlobalEventService, ) { @@ -28,7 +36,8 @@ export class SigninService { @bindThis public signin(request: FastifyRequest, reply: FastifyReply, user: MiLocalUser) { setImmediate(async () => { - // Append signin history + this.notificationService.createNotification(user.id, 'login', {}); + const record = await this.signinsRepository.insertOne({ id: this.idService.gen(), userId: user.id, @@ -37,15 +46,22 @@ export class SigninService { success: true, }); - // Publish signin event this.globalEventService.publishMainStream(user.id, 'signin', await this.signinEntityService.pack(record)); + + const profile = await this.userProfilesRepository.findOneByOrFail({ userId: user.id }); + if (profile.email && profile.emailVerified) { + this.emailService.sendEmail(profile.email, 'New login / ãƒã‚°ã‚¤ãƒ³ãŒã‚りã¾ã—ãŸ', + 'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / æ–°ã—ã„ãƒã‚°ã‚¤ãƒ³ãŒã‚りã¾ã—ãŸã€‚ã“ã®ãƒã‚°ã‚¤ãƒ³ã«å¿ƒå½“ãŸã‚ŠãŒãªã„å ´åˆã¯ã€ãƒ‘スワードを変更ã™ã‚‹ãªã©ã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ã‚»ã‚ュリティ状態を更新ã—ã¦ãã ã•ã„。', + 'There is a new login. If you do not recognize this login, update the security status of your account, including changing your password. / æ–°ã—ã„ãƒã‚°ã‚¤ãƒ³ãŒã‚りã¾ã—ãŸã€‚ã“ã®ãƒã‚°ã‚¤ãƒ³ã«å¿ƒå½“ãŸã‚ŠãŒãªã„å ´åˆã¯ã€ãƒ‘スワードを変更ã™ã‚‹ãªã©ã€ã‚¢ã‚«ã‚¦ãƒ³ãƒˆã®ã‚»ã‚ュリティ状態を更新ã—ã¦ãã ã•ã„。'); + } }); reply.code(200); return { + finished: true, id: user.id, - i: user.token, - }; + i: user.token!, + } satisfies Misskey.entities.SigninFlowResponse; } } diff --git a/packages/backend/src/server/api/SignupApiService.ts b/packages/backend/src/server/api/SignupApiService.ts index db860d710a..7aea6a0e56 100644 --- a/packages/backend/src/server/api/SignupApiService.ts +++ b/packages/backend/src/server/api/SignupApiService.ts @@ -73,6 +73,7 @@ export class SignupApiService { 'turnstile-response'?: string; 'm-captcha-response'?: string; 'frc-captcha-solution'?: string; + 'testcaptcha-response'?: string; } }>, reply: FastifyReply, @@ -111,6 +112,12 @@ export class SignupApiService { throw new FastifyReplyError(400, err); }); } + + if (this.meta.enableTestcaptcha) { + await this.captchaService.verifyTestcaptcha(body['testcaptcha-response']).catch(err => { + throw new FastifyReplyError(400, err); + }); + } } const username = body['username']; diff --git a/packages/backend/src/server/api/endpoints.ts b/packages/backend/src/server/api/endpoints.ts index 269afbf14b..b4f36234f0 100644 --- a/packages/backend/src/server/api/endpoints.ts +++ b/packages/backend/src/server/api/endpoints.ts @@ -75,6 +75,8 @@ import * as ep___admin_relays_list from './endpoints/admin/relays/list.js'; import * as ep___admin_relays_remove from './endpoints/admin/relays/remove.js'; import * as ep___admin_resetPassword from './endpoints/admin/reset-password.js'; import * as ep___admin_resolveAbuseUserReport from './endpoints/admin/resolve-abuse-user-report.js'; +import * as ep___admin_forwardAbuseUserReport from './endpoints/admin/forward-abuse-user-report.js'; +import * as ep___admin_updateAbuseUserReport from './endpoints/admin/update-abuse-user-report.js'; import * as ep___admin_sendEmail from './endpoints/admin/send-email.js'; import * as ep___admin_serverInfo from './endpoints/admin/server-info.js'; import * as ep___admin_showModerationLogs from './endpoints/admin/show-moderation-logs.js'; @@ -478,6 +480,8 @@ const eps = [ ['admin/relays/remove', ep___admin_relays_remove], ['admin/reset-password', ep___admin_resetPassword], ['admin/resolve-abuse-user-report', ep___admin_resolveAbuseUserReport], + ['admin/forward-abuse-user-report', ep___admin_forwardAbuseUserReport], + ['admin/update-abuse-user-report', ep___admin_updateAbuseUserReport], ['admin/send-email', ep___admin_sendEmail], ['admin/server-info', ep___admin_serverInfo], ['admin/show-moderation-logs', ep___admin_showModerationLogs], diff --git a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts index cf3f257ca6..0dbfaae054 100644 --- a/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts +++ b/packages/backend/src/server/api/endpoints/admin/abuse-user-reports.ts @@ -71,9 +71,22 @@ export const meta = { }, assignee: { type: 'object', - nullable: true, optional: true, + nullable: true, optional: false, ref: 'UserDetailedNotMe', }, + forwarded: { + type: 'boolean', + nullable: false, optional: false, + }, + resolvedAs: { + type: 'string', + nullable: true, optional: false, + enum: ['accept', 'reject', null], + }, + moderationNote: { + type: 'string', + nullable: false, optional: false, + }, }, }, }, @@ -88,7 +101,6 @@ export const paramDef = { state: { type: 'string', nullable: true, default: null }, reporterOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, targetUserOrigin: { type: 'string', enum: ['combined', 'local', 'remote'], default: 'combined' }, - forwarded: { type: 'boolean', default: false }, }, required: [], } as const; diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts index 7754899b95..53b1c4c4ec 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/create.ts @@ -3,33 +3,36 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { Injectable } from '@nestjs/common'; +import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { UsersRepository } from '@/models/_.js'; import { MiAccessToken, MiUser } from '@/models/_.js'; import { SignupService } from '@/core/SignupService.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { InstanceActorService } from '@/core/InstanceActorService.js'; import { localUsernameSchema, passwordSchema } from '@/models/User.js'; +import { DI } from '@/di-symbols.js'; +import type { Config } from '@/config.js'; +import { ApiError } from '@/server/api/error.js'; import { Packed } from '@/misc/json-schema.js'; import { RoleService } from '@/core/RoleService.js'; -import { ApiError } from '@/server/api/error.js'; export const meta = { tags: ['admin'], - res: { - type: 'object', - optional: false, nullable: false, - ref: 'MeDetailed', - properties: { - token: { - type: 'string', - optional: false, nullable: false, - }, + errors: { + accessDenied: { + message: 'Access denied.', + code: 'ACCESS_DENIED', + id: '1fb7cb09-d46a-4fff-b8df-057708cce513', + }, + + wrongInitialPassword: { + message: 'Initial password is incorrect.', + code: 'INCORRECT_INITIAL_PASSWORD', + id: '97147c55-1ae1-4f6f-91d6-e1c3e0e76d62', }, - }, - errors: { // From ApiCallService.ts noCredential: { message: 'Credential required.', @@ -51,6 +54,18 @@ export const meta = { }, }, + res: { + type: 'object', + optional: false, nullable: false, + ref: 'MeDetailed', + properties: { + token: { + type: 'string', + optional: false, nullable: false, + }, + }, + }, + // Required token permissions, but we need to check them manually. // ApiCallService checks access in a way that would prevent creating the first account. softPermissions: [ @@ -64,6 +79,7 @@ export const paramDef = { properties: { username: localUsernameSchema, password: passwordSchema, + setupPassword: { type: 'string', nullable: true }, }, required: ['username', 'password'], } as const; @@ -71,13 +87,49 @@ export const paramDef = { @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( + @Inject(DI.config) + private config: Config, + + @Inject(DI.usersRepository) + private usersRepository: UsersRepository, + private roleService: RoleService, private userEntityService: UserEntityService, private signupService: SignupService, private instanceActorService: InstanceActorService, ) { super(meta, paramDef, async (ps, _me, token) => { - await this.ensurePermissions(_me, token); + const me = _me ? await this.usersRepository.findOneByOrFail({ id: _me.id }) : null; + const realUsers = await this.instanceActorService.realLocalUsersPresent(); + + if (!realUsers && me == null && token == null) { + // åˆå›žã‚»ãƒƒãƒˆã‚¢ãƒƒãƒ—ã®å ´åˆ + if (this.config.setupPassword != null) { + // åˆæœŸãƒ‘スワードãŒè¨å®šã•れã¦ã„ã‚‹å ´åˆ + if (ps.setupPassword !== this.config.setupPassword) { + // åˆæœŸãƒ‘スワードãŒé•ã†å ´åˆ + throw new ApiError(meta.errors.wrongInitialPassword); + } + } else if (ps.setupPassword != null && ps.setupPassword.trim() !== '') { + // åˆæœŸãƒ‘スワードãŒè¨å®šã•れã¦ã„ãªã„ã®ã«åˆæœŸãƒ‘スワードãŒå…¥åŠ›ã•れãŸå ´åˆ + throw new ApiError(meta.errors.wrongInitialPassword); + } + } else { + if (token && !meta.softPermissions.every(p => token.permission.includes(p))) { + // Tokens have scoped permissions which may be *less* than the user's official role, so we need to check. + throw new ApiError(meta.errors.noPermission); + } + + if (me && !await this.roleService.isAdministrator(me)) { + // Only administrators (including root) can create users. + throw new ApiError(meta.errors.noAdmin); + } + + // Anonymous access is only allowed for initial instance setup (this check may be redundant) + if (!me && realUsers) { + throw new ApiError(meta.errors.noCredential); + } + } const { account, secret } = await this.signupService.signup({ username: ps.username, @@ -96,21 +148,4 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- return res; }); } - - private async ensurePermissions(me: MiUser | null, token: MiAccessToken | null): Promise<void> { - // Tokens have scoped permissions which may be *less* than the user's official role, so we need to check. - if (token && !meta.softPermissions.every(p => token.permission.includes(p))) { - throw new ApiError(meta.errors.noPermission); - } - - // Only administrators (including root) can create users. - if (me && !await this.roleService.isAdministrator(me)) { - throw new ApiError(meta.errors.noAdmin); - } - - // Anonymous access is only allowed for initial instance setup. - if (!me && await this.instanceActorService.realLocalUsersPresent()) { - throw new ApiError(meta.errors.noCredential); - } - } } diff --git a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts index 01dea703a3..ece1984cff 100644 --- a/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts +++ b/packages/backend/src/server/api/endpoints/admin/accounts/delete.ts @@ -46,7 +46,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new Error('cannot delete a root account'); } - await this.deleteAccoountService.deleteAccount(user); + await this.deleteAccoountService.deleteAccount(user, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts index 2dae1df87d..b8bfda73a4 100644 --- a/packages/backend/src/server/api/endpoints/admin/announcements/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/announcements/create.ts @@ -55,7 +55,7 @@ export const paramDef = { properties: { title: { type: 'string', minLength: 1 }, text: { type: 'string', minLength: 1 }, - imageUrl: { type: 'string', nullable: true, minLength: 1 }, + imageUrl: { type: 'string', nullable: true, minLength: 0 }, icon: { type: 'string', enum: ['info', 'warning', 'error', 'success'], default: 'info' }, display: { type: 'string', enum: ['normal', 'banner', 'dialog'], default: 'normal' }, forExistingUsers: { type: 'boolean', default: false }, @@ -76,7 +76,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- updatedAt: null, title: ps.title, text: ps.text, - imageUrl: ps.imageUrl, + /* eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing -- ç©ºã®æ–‡å—列ã®å ´åˆã€nullを渡ã™ã‚ˆã†ã«ã™ã‚‹ãŸã‚ */ + imageUrl: ps.imageUrl || null, icon: ps.icon, display: ps.display, forExistingUsers: ps.forExistingUsers, diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts index fd21309818..87d80cbe80 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/create.ts @@ -6,6 +6,7 @@ import { Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; +import { IdService } from '@/core/IdService.js'; export const meta = { tags: ['admin'], @@ -13,6 +14,49 @@ export const meta = { requireCredential: true, requireRolePolicy: 'canManageAvatarDecorations', kind: 'write:admin:avatar-decorations', + + res: { + type: 'object', + optional: false, nullable: false, + properties: { + id: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + createdAt: { + type: 'string', + optional: false, nullable: false, + format: 'date-time', + }, + updatedAt: { + type: 'string', + optional: false, nullable: true, + format: 'date-time', + }, + name: { + type: 'string', + optional: false, nullable: false, + }, + description: { + type: 'string', + optional: false, nullable: false, + }, + url: { + type: 'string', + optional: false, nullable: false, + }, + roleIdsThatCanBeUsedThisDecoration: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + optional: false, nullable: false, + format: 'id', + }, + }, + }, + }, } as const; export const paramDef = { @@ -32,14 +76,25 @@ export const paramDef = { export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( private avatarDecorationService: AvatarDecorationService, + private idService: IdService, ) { super(meta, paramDef, async (ps, me) => { - await this.avatarDecorationService.create({ + const created = await this.avatarDecorationService.create({ name: ps.name, description: ps.description, url: ps.url, roleIdsThatCanBeUsedThisDecoration: ps.roleIdsThatCanBeUsedThisDecoration, }, me); + + return { + id: created.id, + createdAt: this.idService.parse(created.id).date.toISOString(), + updatedAt: null, + name: created.name, + description: created.description, + url: created.url, + roleIdsThatCanBeUsedThisDecoration: created.roleIdsThatCanBeUsedThisDecoration, + }; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts index aee90023e1..d785f085ac 100644 --- a/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts +++ b/packages/backend/src/server/api/endpoints/admin/avatar-decorations/list.ts @@ -4,10 +4,7 @@ */ import { Inject, Injectable } from '@nestjs/common'; -import type { AnnouncementsRepository, AnnouncementReadsRepository } from '@/models/_.js'; -import type { MiAnnouncement } from '@/models/Announcement.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; -import { QueryService } from '@/core/QueryService.js'; import { DI } from '@/di-symbols.js'; import { IdService } from '@/core/IdService.js'; import { AvatarDecorationService } from '@/core/AvatarDecorationService.js'; diff --git a/packages/backend/src/server/api/endpoints/admin/delete-account.ts b/packages/backend/src/server/api/endpoints/admin/delete-account.ts index b6f0f22d60..9065a71f6a 100644 --- a/packages/backend/src/server/api/endpoints/admin/delete-account.ts +++ b/packages/backend/src/server/api/endpoints/admin/delete-account.ts @@ -33,13 +33,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private deleteAccountService: DeleteAccountService, ) { - super(meta, paramDef, async (ps) => { + super(meta, paramDef, async (ps, me) => { const user = await this.usersRepository.findOneByOrFail({ id: ps.userId }); if (user.isDeleted) { return; } - await this.deleteAccountService.deleteAccount(user); + await this.deleteAccountService.deleteAccount(user, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts index 3caa0f84a3..071ddbef18 100644 --- a/packages/backend/src/server/api/endpoints/admin/emoji/update.ts +++ b/packages/backend/src/server/api/endpoints/admin/emoji/update.ts @@ -6,7 +6,7 @@ import { Inject, Injectable } from '@nestjs/common'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { CustomEmojiService } from '@/core/CustomEmojiService.js'; -import type { DriveFilesRepository } from '@/models/_.js'; +import type { DriveFilesRepository, MiEmoji } from '@/models/_.js'; import { DI } from '@/di-symbols.js'; import { ApiError } from '../../../error.js'; @@ -79,25 +79,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (driveFile == null) throw new ApiError(meta.errors.noSuchFile); } - let emojiId; - if (ps.id) { - emojiId = ps.id; - const emoji = await this.customEmojiService.getEmojiById(ps.id); - if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); - if (nameNfc && (nameNfc !== emoji.name)) { - const isDuplicate = await this.customEmojiService.checkDuplicate(nameNfc); - if (isDuplicate) throw new ApiError(meta.errors.sameNameEmojiExists); - } - } else { - if (!nameNfc) throw new Error('Invalid Params unexpectedly passed. This is a BUG. Please report it to the development team.'); - const emoji = await this.customEmojiService.getEmojiByName(nameNfc); - if (!emoji) throw new ApiError(meta.errors.noSuchEmoji); - emojiId = emoji.id; - } + // JSON schemeã®anyOfã®åž‹å¤‰æ›ãŒã†ã¾ãã„ã£ã¦ã„ãªã„らã—ã„ + const required = { id: ps.id, name: nameNfc } as + | { id: MiEmoji['id']; name?: string } + | { id?: MiEmoji['id']; name: string }; - await this.customEmojiService.update(emojiId, { + const error = await this.customEmojiService.update({ + ...required, driveFile, - name: nameNfc, category: ps.category?.normalize('NFC'), aliases: ps.aliases?.map(a => a.normalize('NFC')), license: ps.license, @@ -105,6 +94,14 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- localOnly: ps.localOnly, roleIdsThatCanBeUsedThisEmojiAsReaction: ps.roleIdsThatCanBeUsedThisEmojiAsReaction, }, me); + + switch (error) { + case null: return; + case 'NO_SUCH_EMOJI': throw new ApiError(meta.errors.noSuchEmoji); + case 'SAME_NAME_EMOJI_EXISTS': throw new ApiError(meta.errors.sameNameEmojiExists); + } + // 網羅性ãƒã‚§ãƒƒã‚¯ + const mustBeNever: never = error; }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts new file mode 100644 index 0000000000..3e42c91fed --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/forward-abuse-user-report.ts @@ -0,0 +1,55 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { AbuseUserReportsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { AbuseReportService } from '@/core/AbuseReportService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:resolve-abuse-user-report', + + errors: { + noSuchAbuseReport: { + message: 'No such abuse report.', + code: 'NO_SUCH_ABUSE_REPORT', + id: '8763e21b-d9bc-40be-acf6-54c1a6986493', + kind: 'server', + httpStatusCode: 404, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + reportId: { type: 'string', format: 'misskey:id' }, + }, + required: ['reportId'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + private abuseReportService: AbuseReportService, + ) { + super(meta, paramDef, async (ps, me) => { + const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId }); + if (!report) { + throw new ApiError(meta.errors.noSuchAbuseReport); + } + + await this.abuseReportService.forward(report.id, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/meta.ts b/packages/backend/src/server/api/endpoints/admin/meta.ts index 6e368eff43..6495e3b7da 100644 --- a/packages/backend/src/server/api/endpoints/admin/meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/meta.ts @@ -81,6 +81,10 @@ export const meta = { type: 'string', optional: false, nullable: true, }, + enableTestcaptcha: { + type: 'boolean', + optional: false, nullable: false, + }, swPublickey: { type: 'string', optional: false, nullable: true, @@ -189,6 +193,13 @@ export const meta = { type: 'string', }, }, + prohibitedWordsForNameOfUser: { + type: 'array', + optional: false, nullable: false, + items: { + type: 'string', + }, + }, bannedEmailDomains: { type: 'array', optional: true, nullable: false, @@ -368,6 +379,10 @@ export const meta = { type: 'boolean', optional: false, nullable: false, }, + enableStatsForFederatedInstances: { + type: 'boolean', + optional: false, nullable: false, + }, enableServerMachineStats: { type: 'boolean', optional: false, nullable: false, @@ -614,6 +629,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- turnstileSiteKey: instance.turnstileSiteKey, enableFC: instance.enableFC, fcSiteKey: instance.fcSiteKey, + enableTestcaptcha: instance.enableTestcaptcha, swPublickey: instance.swPublicKey, themeColor: instance.themeColor, mascotImageUrl: instance.mascotImageUrl, @@ -642,6 +658,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- mediaSilencedHosts: instance.mediaSilencedHosts, sensitiveWords: instance.sensitiveWords, prohibitedWords: instance.prohibitedWords, + prohibitedWordsForNameOfUser: instance.prohibitedWordsForNameOfUser, preservedUsernames: instance.preservedUsernames, bubbleInstances: instance.bubbleInstances, hcaptchaSecretKey: instance.hcaptchaSecretKey, @@ -688,6 +705,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- truemailAuthKey: instance.truemailAuthKey, enableChartsForRemoteUser: instance.enableChartsForRemoteUser, enableChartsForFederatedInstances: instance.enableChartsForFederatedInstances, + enableStatsForFederatedInstances: instance.enableStatsForFederatedInstances, enableServerMachineStats: instance.enableServerMachineStats, enableAchievements: instance.enableAchievements, enableIdenticonGeneration: instance.enableIdenticonGeneration, diff --git a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts index 9b79100fcf..554d324ff2 100644 --- a/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts +++ b/packages/backend/src/server/api/endpoints/admin/resolve-abuse-user-report.ts @@ -32,7 +32,7 @@ export const paramDef = { type: 'object', properties: { reportId: { type: 'string', format: 'misskey:id' }, - forward: { type: 'boolean', default: false }, + resolvedAs: { type: 'string', enum: ['accept', 'reject', null], nullable: true }, }, required: ['reportId'], } as const; @@ -50,7 +50,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.noSuchAbuseReport); } - await this.abuseReportService.resolve([{ reportId: report.id, forward: ps.forward }], me); + await this.abuseReportService.resolve([{ reportId: report.id, resolvedAs: ps.resolvedAs ?? null }], me); }); } } diff --git a/packages/backend/src/server/api/endpoints/admin/show-users.ts b/packages/backend/src/server/api/endpoints/admin/show-users.ts index 5f16519403..cc65ed2cf0 100644 --- a/packages/backend/src/server/api/endpoints/admin/show-users.ts +++ b/packages/backend/src/server/api/endpoints/admin/show-users.ts @@ -72,13 +72,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- break; } case 'moderator': { - const moderatorIds = await this.roleService.getModeratorIds(false); + const moderatorIds = await this.roleService.getModeratorIds({ includeAdmins: false }); if (moderatorIds.length === 0) return []; query.where('user.id IN (:...moderatorIds)', { moderatorIds: moderatorIds }); break; } case 'adminOrModerator': { - const adminOrModeratorIds = await this.roleService.getModeratorIds(); + const adminOrModeratorIds = await this.roleService.getModeratorIds({ includeAdmins: true }); if (adminOrModeratorIds.length === 0) return []; query.where('user.id IN (:...adminOrModeratorIds)', { adminOrModeratorIds: adminOrModeratorIds }); break; diff --git a/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts b/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts new file mode 100644 index 0000000000..73d4b843f0 --- /dev/null +++ b/packages/backend/src/server/api/endpoints/admin/update-abuse-user-report.ts @@ -0,0 +1,58 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Inject, Injectable } from '@nestjs/common'; +import { Endpoint } from '@/server/api/endpoint-base.js'; +import type { AbuseUserReportsRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { ApiError } from '@/server/api/error.js'; +import { AbuseReportService } from '@/core/AbuseReportService.js'; + +export const meta = { + tags: ['admin'], + + requireCredential: true, + requireModerator: true, + kind: 'write:admin:resolve-abuse-user-report', + + errors: { + noSuchAbuseReport: { + message: 'No such abuse report.', + code: 'NO_SUCH_ABUSE_REPORT', + id: '15f51cf5-46d1-4b1d-a618-b35bcbed0662', + kind: 'server', + httpStatusCode: 404, + }, + }, +} as const; + +export const paramDef = { + type: 'object', + properties: { + reportId: { type: 'string', format: 'misskey:id' }, + moderationNote: { type: 'string' }, + }, + required: ['reportId'], +} as const; + +@Injectable() +export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export + constructor( + @Inject(DI.abuseUserReportsRepository) + private abuseUserReportsRepository: AbuseUserReportsRepository, + private abuseReportService: AbuseReportService, + ) { + super(meta, paramDef, async (ps, me) => { + const report = await this.abuseUserReportsRepository.findOneBy({ id: ps.reportId }); + if (!report) { + throw new ApiError(meta.errors.noSuchAbuseReport); + } + + await this.abuseReportService.update(report.id, { + moderationNote: ps.moderationNote, + }, me); + }); + } +} diff --git a/packages/backend/src/server/api/endpoints/admin/update-meta.ts b/packages/backend/src/server/api/endpoints/admin/update-meta.ts index 98760bbcc3..72f428d85f 100644 --- a/packages/backend/src/server/api/endpoints/admin/update-meta.ts +++ b/packages/backend/src/server/api/endpoints/admin/update-meta.ts @@ -46,6 +46,11 @@ export const paramDef = { type: 'string', }, }, + prohibitedWordsForNameOfUser: { + type: 'array', nullable: true, items: { + type: 'string', + }, + }, themeColor: { type: 'string', nullable: true, pattern: '^#[0-9a-fA-F]{6}$' }, mascotImageUrl: { type: 'string', nullable: true }, bannerUrl: { type: 'string', nullable: true }, @@ -84,6 +89,7 @@ export const paramDef = { enableFC: { type: 'boolean' }, fcSiteKey: { type: 'string', nullable: true }, fcSecretKey: { type: 'string', nullable: true }, + enableTestcaptcha: { type: 'boolean' }, sensitiveMediaDetection: { type: 'string', enum: ['none', 'all', 'local', 'remote'] }, sensitiveMediaDetectionSensitivity: { type: 'string', enum: ['medium', 'low', 'high', 'veryLow', 'veryHigh'] }, setSensitiveFlagAutomatically: { type: 'boolean' }, @@ -140,6 +146,7 @@ export const paramDef = { truemailAuthKey: { type: 'string', nullable: true }, enableChartsForRemoteUser: { type: 'boolean' }, enableChartsForFederatedInstances: { type: 'boolean' }, + enableStatsForFederatedInstances: { type: 'boolean' }, enableServerMachineStats: { type: 'boolean' }, enableAchievements: { type: 'boolean' }, enableIdenticonGeneration: { type: 'boolean' }, @@ -230,6 +237,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (Array.isArray(ps.prohibitedWords)) { set.prohibitedWords = ps.prohibitedWords.filter(Boolean); } + if (Array.isArray(ps.prohibitedWordsForNameOfUser)) { + set.prohibitedWordsForNameOfUser = ps.prohibitedWordsForNameOfUser.filter(Boolean); + } if (Array.isArray(ps.silencedHosts)) { let lastValue = ''; set.silencedHosts = ps.silencedHosts.sort().filter((h) => { @@ -390,6 +400,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- set.enableFC = ps.enableFC; } + if (ps.enableTestcaptcha !== undefined) { + set.enableTestcaptcha = ps.enableTestcaptcha; + } + if (ps.fcSiteKey !== undefined) { set.fcSiteKey = ps.fcSiteKey; } @@ -610,6 +624,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- set.enableChartsForFederatedInstances = ps.enableChartsForFederatedInstances; } + if (ps.enableStatsForFederatedInstances !== undefined) { + set.enableStatsForFederatedInstances = ps.enableStatsForFederatedInstances; + } + if (ps.enableServerMachineStats !== undefined) { set.enableServerMachineStats = ps.enableServerMachineStats; } @@ -709,7 +727,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- } if (Array.isArray(ps.federationHosts)) { - set.blockedHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); + set.federationHosts = ps.federationHosts.filter(Boolean).map(x => x.toLowerCase()); } const before = await this.metaService.fetch(true); diff --git a/packages/backend/src/server/api/endpoints/ap/show.ts b/packages/backend/src/server/api/endpoints/ap/show.ts index 4232bc6e39..616a77e337 100644 --- a/packages/backend/src/server/api/endpoints/ap/show.ts +++ b/packages/backend/src/server/api/endpoints/ap/show.ts @@ -137,6 +137,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (local != null) return local; } + // åŒä¸€ãƒ¦ãƒ¼ã‚¶ãƒ¼ã®æƒ…å ±ã‚’å†åº¦å‡¦ç†ã™ã‚‹ã®ã§ã€ä½¿ç”¨æ¸ˆã¿ã®resolverã‚’å†åˆ©ç”¨ã—ã¦ã¯ã„ã‘ãªã„ return await this.mergePack( me, isActor(object) ? await this.apPersonService.createPerson(getApId(object)) : null, diff --git a/packages/backend/src/server/api/endpoints/flash/featured.ts b/packages/backend/src/server/api/endpoints/flash/featured.ts index 2e8cbffe2a..ad1f35055a 100644 --- a/packages/backend/src/server/api/endpoints/flash/featured.ts +++ b/packages/backend/src/server/api/endpoints/flash/featured.ts @@ -8,6 +8,7 @@ import type { FlashsRepository } from '@/models/_.js'; import { Endpoint } from '@/server/api/endpoint-base.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { DI } from '@/di-symbols.js'; +import { FlashService } from '@/core/FlashService.js'; export const meta = { tags: ['flash'], @@ -33,26 +34,25 @@ export const meta = { export const paramDef = { type: 'object', - properties: {}, + properties: { + offset: { type: 'integer', minimum: 0, default: 0 }, + limit: { type: 'integer', minimum: 1, maximum: 100, default: 10 }, + }, required: [], } as const; @Injectable() export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint-disable-line import/no-default-export constructor( - @Inject(DI.flashsRepository) - private flashsRepository: FlashsRepository, - + private flashService: FlashService, private flashEntityService: FlashEntityService, ) { super(meta, paramDef, async (ps, me) => { - const query = this.flashsRepository.createQueryBuilder('flash') - .andWhere('flash.likedCount > 0') - .orderBy('flash.likedCount', 'DESC'); - - const flashs = await query.limit(10).getMany(); - - return await this.flashEntityService.packMany(flashs, me); + const result = await this.flashService.featured({ + offset: ps.offset, + limit: ps.limit, + }); + return await this.flashEntityService.packMany(result, me); }); } } diff --git a/packages/backend/src/server/api/endpoints/i/update.ts b/packages/backend/src/server/api/endpoints/i/update.ts index c22cbe5d4b..bb0f5aa11a 100644 --- a/packages/backend/src/server/api/endpoints/i/update.ts +++ b/packages/backend/src/server/api/endpoints/i/update.ts @@ -11,7 +11,7 @@ import { JSDOM } from 'jsdom'; import { extractCustomEmojisFromMfm } from '@/misc/extract-custom-emojis-from-mfm.js'; import { extractHashtags } from '@/misc/extract-hashtags.js'; import * as Acct from '@/misc/acct.js'; -import type { UsersRepository, DriveFilesRepository, UserProfilesRepository, PagesRepository } from '@/models/_.js'; +import type { UsersRepository, DriveFilesRepository, MiMeta, UserProfilesRepository, PagesRepository } from '@/models/_.js'; import type { MiLocalUser, MiUser } from '@/models/User.js'; import { birthdaySchema, listenbrainzSchema, descriptionSchema, followedMessageSchema, locationSchema, nameSchema } from '@/models/User.js'; import type { MiUserProfile } from '@/models/UserProfile.js'; @@ -22,6 +22,7 @@ import { UserEntityService } from '@/core/entities/UserEntityService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { UserFollowingService } from '@/core/UserFollowingService.js'; import { AccountUpdateService } from '@/core/AccountUpdateService.js'; +import { UtilityService } from '@/core/UtilityService.js'; import { HashtagService } from '@/core/HashtagService.js'; import { DI } from '@/di-symbols.js'; import { RolePolicies, RoleService } from '@/core/RoleService.js'; @@ -126,6 +127,13 @@ export const meta = { code: 'RESTRICTED_BY_ROLE', id: '8feff0ba-5ab5-585b-31f4-4df816663fad', }, + + nameContainsProhibitedWords: { + message: 'Your new name contains prohibited words.', + code: 'YOUR_NAME_CONTAINS_PROHIBITED_WORDS', + id: '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191', + httpStatusCode: 422, + }, }, res: { @@ -187,6 +195,9 @@ export const paramDef = { noCrawle: { type: 'boolean' }, preventAiLearning: { type: 'boolean' }, noindex: { type: 'boolean' }, + requireSigninToViewContents: { type: 'boolean' }, + makeNotesFollowersOnlyBefore: { type: 'integer', nullable: true }, + makeNotesHiddenBefore: { type: 'integer', nullable: true }, enableRss: { type: 'boolean' }, isBot: { type: 'boolean' }, isCat: { type: 'boolean' }, @@ -242,6 +253,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- @Inject(DI.config) private config: Config, + @Inject(DI.meta) + private instanceMeta: MiMeta, + @Inject(DI.usersRepository) private usersRepository: UsersRepository, @@ -266,6 +280,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- private cacheService: CacheService, private httpRequestService: HttpRequestService, private avatarDecorationService: AvatarDecorationService, + private utilityService: UtilityService, ) { super(meta, paramDef, async (ps, _user, token) => { const user = await this.usersRepository.findOneByOrFail({ id: _user.id }) as MiLocalUser; @@ -343,6 +358,9 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- if (typeof ps.carefulBot === 'boolean') profileUpdates.carefulBot = ps.carefulBot; if (typeof ps.autoAcceptFollowed === 'boolean') profileUpdates.autoAcceptFollowed = ps.autoAcceptFollowed; if (typeof ps.noCrawle === 'boolean') profileUpdates.noCrawle = ps.noCrawle; + if (typeof ps.requireSigninToViewContents === 'boolean') updates.requireSigninToViewContents = ps.requireSigninToViewContents; + if ((typeof ps.makeNotesFollowersOnlyBefore === 'number') || (ps.makeNotesFollowersOnlyBefore === null)) updates.makeNotesFollowersOnlyBefore = ps.makeNotesFollowersOnlyBefore; + if ((typeof ps.makeNotesHiddenBefore === 'number') || (ps.makeNotesHiddenBefore === null)) updates.makeNotesHiddenBefore = ps.makeNotesHiddenBefore; if (typeof ps.isCat === 'boolean') updates.isCat = ps.isCat; if (typeof ps.speakAsCat === 'boolean') updates.speakAsCat = ps.speakAsCat; if (typeof ps.injectFeaturedNote === 'boolean') profileUpdates.injectFeaturedNote = ps.injectFeaturedNote; @@ -485,8 +503,17 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- const newName = updates.name === undefined ? user.name : updates.name; const newDescription = profileUpdates.description === undefined ? profile.description : profileUpdates.description; const newFields = profileUpdates.fields === undefined ? profile.fields : profileUpdates.fields; + const newFollowedMessage = profileUpdates.followedMessage === undefined ? profile.followedMessage : profileUpdates.followedMessage; if (newName != null) { + let hasProhibitedWords = false; + if (!await this.roleService.isModerator(user)) { + hasProhibitedWords = this.utilityService.isKeyWordIncluded(newName, this.instanceMeta.prohibitedWordsForNameOfUser); + } + if (hasProhibitedWords) { + throw new ApiError(meta.errors.nameContainsProhibitedWords); + } + const tokens = mfm.parseSimple(newName); emojis = emojis.concat(extractCustomEmojisFromMfm(tokens)); } @@ -506,6 +533,11 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ]); } + if (newFollowedMessage != null) { + const tokens = mfm.parse(newFollowedMessage); + emojis = emojis.concat(extractCustomEmojisFromMfm(tokens)); + } + updates.emojis = emojis; updates.tags = tags; @@ -589,7 +621,7 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- // these two methods need to be kept in sync with // `ApRendererService.renderPerson` private userNeedsPublishing(oldUser: MiLocalUser, newUser: Partial<MiUser>): boolean { - const basicFields: (keyof MiUser)[] = ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs', 'hideOnlineStatus', 'enableRss']; + const basicFields: (keyof MiUser)[] = ['avatarId', 'bannerId', 'backgroundId', 'isBot', 'username', 'name', 'isLocked', 'isExplorable', 'isCat', 'noindex', 'speakAsCat', 'movedToUri', 'alsoKnownAs', 'hideOnlineStatus', 'enableRss', 'requireSigninToViewContents', 'makeNotesFollowersOnlyBefore', 'makeNotesHiddenBefore']; for (const field of basicFields) { if ((field in newUser) && oldUser[field] !== newUser[field]) { return true; diff --git a/packages/backend/src/server/api/endpoints/notes/show.ts b/packages/backend/src/server/api/endpoints/notes/show.ts index 49c51cb33c..f0c9db38b4 100644 --- a/packages/backend/src/server/api/endpoints/notes/show.ts +++ b/packages/backend/src/server/api/endpoints/notes/show.ts @@ -28,6 +28,12 @@ export const meta = { code: 'NO_SUCH_NOTE', id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, + + signinRequired: { + message: 'Signin required.', + code: 'SIGNIN_REQUIRED', + id: '8e75455b-738c-471d-9f80-62693f33372e', + }, }, // 2 calls per second @@ -56,7 +62,8 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = await this.notesRepository.createQueryBuilder('note') - .where('note.id = :noteId', { noteId: ps.noteId }); + .where('note.id = :noteId', { noteId: ps.noteId }) + .innerJoinAndSelect('note.user', 'user'); this.queryService.generateVisibilityQuery(query, me); if (me) { @@ -69,6 +76,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.noSuchNote); } + if (note.user!.requireSigninToViewContents && me == null) { + throw new ApiError(meta.errors.signinRequired); + } + return await this.noteEntityService.pack(note, me, { detail: true, }); diff --git a/packages/backend/src/server/api/endpoints/notes/versions.ts b/packages/backend/src/server/api/endpoints/notes/versions.ts index 343417f0e2..9b98d19fb1 100644 --- a/packages/backend/src/server/api/endpoints/notes/versions.ts +++ b/packages/backend/src/server/api/endpoints/notes/versions.ts @@ -27,6 +27,12 @@ export const meta = { code: 'NO_SUCH_NOTE', id: '24fcbfc6-2e37-42b6-8388-c29b3861a08d', }, + + signinRequired: { + message: 'Signin required.', + code: 'SIGNIN_REQUIRED', + id: '8e75455b-738c-471d-9f80-62693f33372e', + }, }, // 10 calls per 5 seconds @@ -55,10 +61,13 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- ) { super(meta, paramDef, async (ps, me) => { const query = await this.notesRepository.createQueryBuilder('note') - .select('note.id') - .where('note.id = :noteId', { noteId: ps.noteId }); + .where('note.id = :noteId', { noteId: ps.noteId }) + .innerJoinAndSelect('note.user', 'user'); this.queryService.generateVisibilityQuery(query, me); + if (me) { + this.queryService.generateBlockedUserQuery(query, me); + } const note = await query.getOne(); @@ -66,6 +75,10 @@ export default class extends Endpoint<typeof meta, typeof paramDef> { // eslint- throw new ApiError(meta.errors.noSuchNote); } + if (note.user!.requireSigninToViewContents && me == null) { + throw new ApiError(meta.errors.signinRequired); + } + const edits = await this.getterService.getEdits(ps.noteId).catch(err => { if (err.id === '9725d0ce-ba28-4dde-95a7-2cbb2c15de24') throw new ApiError(meta.errors.noSuchNote); throw err; diff --git a/packages/backend/src/server/api/endpoints/users/notes.ts b/packages/backend/src/server/api/endpoints/users/notes.ts index 92d8032fa6..6416e43ff1 100644 --- a/packages/backend/src/server/api/endpoints/users/notes.ts +++ b/packages/backend/src/server/api/endpoints/users/notes.ts @@ -43,6 +43,12 @@ export const meta = { code: 'BOTH_WITH_REPLIES_AND_WITH_FILES', id: '91c8cb9f-36ed-46e7-9ca2-7df96ed6e222', }, + + signinRequired: { + message: 'Signin required.', + code: 'SIGNIN_REQUIRED', + id: 'd1588a9e-4b4d-4c07-807f-16f1486577a2', + }, }, // 5 calls per second diff --git a/packages/backend/src/server/web/ClientServerService.ts b/packages/backend/src/server/web/ClientServerService.ts index 42f9b104ff..e59314bf55 100644 --- a/packages/backend/src/server/web/ClientServerService.ts +++ b/packages/backend/src/server/web/ClientServerService.ts @@ -30,6 +30,7 @@ import type { EndedPollNotificationQueue, InboxQueue, ObjectStorageQueue, + RelationshipQueue, SystemQueue, UserWebhookDeliverQueue, SystemWebhookDeliverQueue, @@ -42,13 +43,26 @@ import { MetaEntityService } from '@/core/entities/MetaEntityService.js'; import { GalleryPostEntityService } from '@/core/entities/GalleryPostEntityService.js'; import { ClipEntityService } from '@/core/entities/ClipEntityService.js'; import { ChannelEntityService } from '@/core/entities/ChannelEntityService.js'; -import type { ChannelsRepository, ClipsRepository, FlashsRepository, GalleryPostsRepository, MiMeta, NotesRepository, PagesRepository, ReversiGamesRepository, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import type { + AnnouncementsRepository, + ChannelsRepository, + ClipsRepository, + FlashsRepository, + GalleryPostsRepository, + MiMeta, + NotesRepository, + PagesRepository, + ReversiGamesRepository, + UserProfilesRepository, + UsersRepository, +} from '@/models/_.js'; import type Logger from '@/logger.js'; import { handleRequestRedirectToOmitSearch } from '@/misc/fastify-hook-handlers.js'; import { bindThis } from '@/decorators.js'; import { FlashEntityService } from '@/core/entities/FlashEntityService.js'; import { RoleService } from '@/core/RoleService.js'; import { ReversiGameEntityService } from '@/core/entities/ReversiGameEntityService.js'; +import { AnnouncementEntityService } from '@/core/entities/AnnouncementEntityService.js'; import { FeedService } from './FeedService.js'; import { UrlPreviewService } from './UrlPreviewService.js'; import { ClientLoggerService } from './ClientLoggerService.js'; @@ -103,6 +117,9 @@ export class ClientServerService { @Inject(DI.reversiGamesRepository) private reversiGamesRepository: ReversiGamesRepository, + @Inject(DI.announcementsRepository) + private announcementsRepository: AnnouncementsRepository, + private flashEntityService: FlashEntityService, private userEntityService: UserEntityService, private noteEntityService: NoteEntityService, @@ -112,6 +129,7 @@ export class ClientServerService { private clipEntityService: ClipEntityService, private channelEntityService: ChannelEntityService, private reversiGameEntityService: ReversiGameEntityService, + private announcementEntityService: AnnouncementEntityService, private urlPreviewService: UrlPreviewService, private feedService: FeedService, private roleService: RoleService, @@ -122,6 +140,7 @@ export class ClientServerService { @Inject('queue:deliver') public deliverQueue: DeliverQueue, @Inject('queue:inbox') public inboxQueue: InboxQueue, @Inject('queue:db') public dbQueue: DbQueue, + @Inject('queue:relationship') public relationshipQueue: RelationshipQueue, @Inject('queue:objectStorage') public objectStorageQueue: ObjectStorageQueue, @Inject('queue:userWebhookDeliver') public userWebhookDeliverQueue: UserWebhookDeliverQueue, @Inject('queue:systemWebhookDeliver') public systemWebhookDeliverQueue: SystemWebhookDeliverQueue, @@ -253,6 +272,7 @@ export class ClientServerService { this.deliverQueue, this.inboxQueue, this.dbQueue, + this.relationshipQueue, this.objectStorageQueue, this.userWebhookDeliverQueue, this.systemWebhookDeliverQueue, @@ -561,7 +581,7 @@ export class ClientServerService { } }); - //#region SSR (for crawlers) + //#region SSR // User fastify.get<{ Params: { user: string; sub?: string; } }>('/@:user/:sub?', async (request, reply) => { const { username, host } = Acct.parse(request.params.user); @@ -586,11 +606,20 @@ export class ClientServerService { reply.header('X-Robots-Tag', 'noimageai'); reply.header('X-Robots-Tag', 'noai'); } + + const _user = await this.userEntityService.pack(user, null, { + schema: 'UserDetailed', + userProfile: profile, + }); + return await reply.view('user', { user, profile, me, avatarUrl: user.avatarUrl ?? this.userEntityService.getIdenticonUrl(user), sub: request.params.sub, ...await this.generateCommonPugData(this.meta), + clientCtx: htmlSafeJsonStringify({ + user: _user, + }), }); } else { // リモートユーザーãªã®ã§ @@ -620,12 +649,15 @@ export class ClientServerService { fastify.get<{ Params: { note: string; } }>('/notes/:note', async (request, reply) => { vary(reply.raw, 'Accept'); - const note = await this.notesRepository.findOneBy({ - id: request.params.note, - visibility: In(['public', 'home']), + const note = await this.notesRepository.findOne({ + where: { + id: request.params.note, + visibility: In(['public', 'home']), + }, + relations: ['user'], }); - if (note) { + if (note && !note.user!.requireSigninToViewContents) { const _note = await this.noteEntityService.pack(note); const profile = await this.userProfilesRepository.findOneByOrFail({ userId: note.userId }); reply.header('Cache-Control', 'public, max-age=15'); @@ -640,6 +672,9 @@ export class ClientServerService { // TODO: Let locale changeable by instance setting summary: getNoteSummary(_note), ...await this.generateCommonPugData(this.meta), + clientCtx: htmlSafeJsonStringify({ + note: _note, + }), }); } else { return await renderBase(reply); @@ -728,6 +763,9 @@ export class ClientServerService { profile, avatarUrl: _clip.user.avatarUrl, ...await this.generateCommonPugData(this.meta), + clientCtx: htmlSafeJsonStringify({ + clip: _clip, + }), }); } else { return await renderBase(reply); @@ -792,6 +830,24 @@ export class ClientServerService { return await renderBase(reply); } }); + + // 個別ãŠçŸ¥ã‚‰ã›ãƒšãƒ¼ã‚¸ + fastify.get<{ Params: { announcementId: string; } }>('/announcements/:announcementId', async (request, reply) => { + const announcement = await this.announcementsRepository.findOneBy({ + id: request.params.announcementId, + }); + + if (announcement) { + const _announcement = await this.announcementEntityService.pack(announcement); + reply.header('Cache-Control', 'public, max-age=3600'); + return await reply.view('announcement', { + announcement: _announcement, + ...await this.generateCommonPugData(this.meta), + }); + } else { + return await renderBase(reply); + } + }); //#endregion //#region noindex pages diff --git a/packages/backend/src/server/web/boot.js b/packages/backend/src/server/web/boot.js index ad92480c1c..bf83340bde 100644 --- a/packages/backend/src/server/web/boot.js +++ b/packages/backend/src/server/web/boot.js @@ -108,7 +108,7 @@ } for (const [k, v] of Object.entries(themeProps)) { if (k.startsWith('font')) continue; - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); // HTMLã® theme-color é©ç”¨ if (k === 'htmlThemeColor') { diff --git a/packages/backend/src/server/web/style.css b/packages/backend/src/server/web/style.css index 1cd9cadecf..8094a0f6de 100644 --- a/packages/backend/src/server/web/style.css +++ b/packages/backend/src/server/web/style.css @@ -5,8 +5,8 @@ */ html { - background-color: var(--bg); - color: var(--fg); + background-color: var(--MI_THEME-bg); + color: var(--MI_THEME-fg); } #splash { @@ -17,7 +17,7 @@ html { width: 100vw; height: 100vh; cursor: wait; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); opacity: 1; transition: opacity 0.5s ease; } @@ -45,7 +45,7 @@ html { width: 28px; height: 28px; transform: translateY(80px); - color: var(--accent); + color: var(--MI_THEME-accent); } #splashSpinner > .spinner { diff --git a/packages/backend/src/server/web/style.embed.css b/packages/backend/src/server/web/style.embed.css index a7b110d80a..5e8786cc4e 100644 --- a/packages/backend/src/server/web/style.embed.css +++ b/packages/backend/src/server/web/style.embed.css @@ -5,8 +5,8 @@ */ html { - background-color: var(--bg); - color: var(--fg); + background-color: var(--MI_THEME-bg); + color: var(--MI_THEME-fg); } html.embed { @@ -24,7 +24,7 @@ html.embed { width: 100vw; height: 100vh; cursor: wait; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); opacity: 1; transition: opacity 0.5s ease; } @@ -33,7 +33,7 @@ html.embed #splash { box-sizing: border-box; min-height: 300px; border-radius: var(--radius, 12px); - border: 1px solid var(--divider, #e8e8e8); + border: 1px solid var(--MI_THEME-divider, #e8e8e8); } html.embed.norounded #splash { @@ -67,7 +67,7 @@ html.embed.noborder #splash { width: 28px; height: 28px; transform: translateY(70px); - color: var(--accent); + color: var(--MI_THEME-accent); } #splashSpinner > .spinner { diff --git a/packages/backend/src/server/web/views/announcement.pug b/packages/backend/src/server/web/views/announcement.pug new file mode 100644 index 0000000000..7a4052e8a4 --- /dev/null +++ b/packages/backend/src/server/web/views/announcement.pug @@ -0,0 +1,21 @@ +extends ./base + +block vars + - const title = announcement.title; + - const description = announcement.text.length > 100 ? announcement.text.slice(0, 100) + '…' : announcement.text; + - const url = `${config.url}/announcements/${announcement.id}`; + +block title + = `${title} | ${instanceName}` + +block desc + meta(name='description' content=description) + +block og + meta(property='og:type' content='article') + meta(property='og:title' content= title) + meta(property='og:description' content= description) + meta(property='og:url' content= url) + if announcement.imageUrl + meta(property='og:image' content=announcement.imageUrl) + meta(property='twitter:card' content='summary_large_image') diff --git a/packages/backend/src/server/web/views/base.pug b/packages/backend/src/server/web/views/base.pug index 84c4832802..f57dbbbf4e 100644 --- a/packages/backend/src/server/web/views/base.pug +++ b/packages/backend/src/server/web/views/base.pug @@ -2,6 +2,7 @@ block vars block loadClientEntry - const entry = config.frontendEntry; + - const baseUrl = config.url; doctype html @@ -36,7 +37,7 @@ html link(rel='icon' href= icon || '/favicon.ico') link(rel='apple-touch-icon' href= appleTouchIcon || '/apple-touch-icon.png') link(rel='manifest' href='/manifest.json') - link(rel='search' type='application/opensearchdescription+xml' title=(title || "Sharkey") href=`${url}/opensearch.xml`) + link(rel='search' type='application/opensearchdescription+xml' title=(title || "Sharkey") href=`${baseUrl}/opensearch.xml`) link(rel='prefetch' href=serverErrorImageUrl) link(rel='prefetch' href=infoImageUrl) link(rel='prefetch' href=notFoundImageUrl) @@ -79,6 +80,9 @@ html script(type='application/json' id='misskey_meta' data-generated-at=now) != metaJson + script(type='application/json' id='misskey_clientCtx' data-generated-at=now) + != clientCtx + script include ../boot.js diff --git a/packages/backend/src/types.ts b/packages/backend/src/types.ts index 95f049f768..37bed27fb1 100644 --- a/packages/backend/src/types.ts +++ b/packages/backend/src/types.ts @@ -17,6 +17,7 @@ * roleAssigned - ãƒãƒ¼ãƒ«ãŒä»˜ä¸Žã•れ㟠* achievementEarned - 実績をç²å¾— * exportCompleted - エクスãƒãƒ¼ãƒˆãŒå®Œäº† + * login - ãƒã‚°ã‚¤ãƒ³ * app - アプリ通知 * test - テスト通知(サーãƒãƒ¼å´ï¼‰ */ @@ -35,6 +36,7 @@ export const notificationTypes = [ 'roleAssigned', 'achievementEarned', 'exportCompleted', + 'login', 'scheduledNoteFailed', 'scheduledNotePosted', 'app', @@ -106,6 +108,8 @@ export const moderationLogTypes = [ 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', 'resolveAbuseReport', + 'forwardAbuseReport', + 'updateAbuseReportNote', 'createInvitation', 'createAd', 'updateAd', @@ -300,7 +304,18 @@ export type ModerationLogPayloads = { resolveAbuseReport: { reportId: string; report: any; - forwarded: boolean; + forwarded?: boolean; + resolvedAs?: string | null; + }; + forwardAbuseReport: { + reportId: string; + report: any; + }; + updateAbuseReportNote: { + reportId: string; + report: any; + before: string; + after: string; }; createInvitation: { invitations: any[]; diff --git a/packages/backend/test-federation/.config/example.conf b/packages/backend/test-federation/.config/example.conf new file mode 100644 index 0000000000..83d04eb39d --- /dev/null +++ b/packages/backend/test-federation/.config/example.conf @@ -0,0 +1,70 @@ +# based on https://github.com/misskey-dev/misskey-hub/blob/7071f63a1c80ee35c71f0fd8a6d8722c118c7574/src/docs/admin/nginx.md + +# For WebSocket +map $http_upgrade $connection_upgrade { + default upgrade; + '' close; +} + +proxy_cache_path /tmp/nginx_cache levels=1:2 keys_zone=cache1:16m max_size=1g inactive=720m use_temp_path=off; + +server { + listen 80; + listen [::]:80; + server_name ${HOST}; + + # For SSL domain validation + root /var/www/html; + location /.well-known/acme-challenge/ { allow all; } + location /.well-known/pki-validation/ { allow all; } + location / { return 301 https://$server_name$request_uri; } +} + +server { + listen 443 ssl; + listen [::]:443 ssl; + http2 on; + server_name ${HOST}; + + ssl_session_timeout 1d; + ssl_session_cache shared:ssl_session_cache:10m; + ssl_session_tickets off; + + ssl_trusted_certificate /etc/nginx/certificates/rootCA.crt; + ssl_certificate /etc/nginx/certificates/$server_name.crt; + ssl_certificate_key /etc/nginx/certificates/$server_name.key; + + # SSL protocol settings + ssl_protocols TLSv1.2 TLSv1.3; + ssl_ciphers ECDHE-ECDSA-AES128-GCM-SHA256:ECDHE-RSA-AES128-GCM-SHA256:ECDHE-ECDSA-AES256-GCM-SHA384:ECDHE-RSA-AES256-GCM-SHA384:ECDHE-ECDSA-CHACHA20-POLY1305:ECDHE-RSA-CHACHA20-POLY1305:DHE-RSA-AES128-GCM-SHA256:DHE-RSA-AES256-GCM-SHA384; + ssl_prefer_server_ciphers off; + ssl_stapling on; + ssl_stapling_verify on; + + # Change to your upload limit + client_max_body_size 80m; + + # Proxy to Node + location / { + proxy_pass http://misskey.${HOST}:3000; + proxy_set_header Host $host; + proxy_http_version 1.1; + proxy_redirect off; + + # If it's behind another reverse proxy or CDN, remove the following. + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_set_header X-Forwarded-Proto https; + + # For WebSocket + proxy_set_header Upgrade $http_upgrade; + proxy_set_header Connection $connection_upgrade; + + # Cache settings + proxy_cache cache1; + proxy_cache_lock on; + proxy_cache_use_stale updating; + proxy_force_ranges on; + add_header X-Cache $upstream_cache_status; + } +} diff --git a/packages/backend/test-federation/.config/example.default.yml b/packages/backend/test-federation/.config/example.default.yml new file mode 100644 index 0000000000..28d51ac86e --- /dev/null +++ b/packages/backend/test-federation/.config/example.default.yml @@ -0,0 +1,24 @@ +url: https://${HOST}/ +port: 3000 +db: + host: db.${HOST} + port: 5432 + db: misskey + user: postgres + pass: postgres +dbReplications: false +redis: + host: redis.test + port: 6379 +id: 'aidx' +proxyBypassHosts: + - api.deepl.com + - api-free.deepl.com + - www.recaptcha.net + - hcaptcha.com + - challenges.cloudflare.com +proxyRemoteFiles: true +signToActivityPubGet: true +allowedPrivateNetworks: + - 127.0.0.1/32 + - 172.20.0.0/16 diff --git a/packages/backend/test-federation/.config/example.docker.env b/packages/backend/test-federation/.config/example.docker.env new file mode 100644 index 0000000000..a8af7cce49 --- /dev/null +++ b/packages/backend/test-federation/.config/example.docker.env @@ -0,0 +1,5 @@ +NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt +POSTGRES_DB=misskey +POSTGRES_USER=postgres +POSTGRES_PASSWORD=postgres +MK_VERBOSE=true diff --git a/packages/backend/test-federation/.gitignore b/packages/backend/test-federation/.gitignore new file mode 100644 index 0000000000..e00f952cb5 --- /dev/null +++ b/packages/backend/test-federation/.gitignore @@ -0,0 +1,6 @@ +certificates +volumes +.env +docker.env +*.test.conf +*.test.default.yml diff --git a/packages/backend/test-federation/README.md b/packages/backend/test-federation/README.md new file mode 100644 index 0000000000..967d51f085 --- /dev/null +++ b/packages/backend/test-federation/README.md @@ -0,0 +1,24 @@ +## test-federation +Test federation between two Misskey servers: `a.test` and `b.test`. + +Before testing, you need to build the entire project, and change working directory to here: +```sh +pnpm build +cd packages/backend/test-federation +``` + +First, you need to start servers by executing following commands: +```sh +bash ./setup.sh +docker compose up --scale tester=0 +``` + +Then you can run all tests by a following command: +```sh +docker compose run --no-deps --rm tester +``` + +For testing a specific file, run a following command: +```sh +docker compose run --no-deps --rm tester -- pnpm -F backend test:fed packages/backend/test-federation/test/user.test.ts +``` diff --git a/packages/backend/test-federation/compose.a.yml b/packages/backend/test-federation/compose.a.yml new file mode 100644 index 0000000000..6a305b404c --- /dev/null +++ b/packages/backend/test-federation/compose.a.yml @@ -0,0 +1,64 @@ +services: + a.test: + extends: + file: ./compose.tpl.yml + service: nginx + depends_on: + misskey.a.test: + condition: service_healthy + networks: + - internal_network_a + volumes: + - type: bind + source: ./.config/a.test.conf + target: /etc/nginx/conf.d/a.test.conf + read_only: true + - type: bind + source: ./certificates/a.test.crt + target: /etc/nginx/certificates/a.test.crt + read_only: true + - type: bind + source: ./certificates/a.test.key + target: /etc/nginx/certificates/a.test.key + read_only: true + + misskey.a.test: + extends: + file: ./compose.tpl.yml + service: misskey + depends_on: + db.a.test: + condition: service_healthy + redis.test: + condition: service_healthy + setup: + condition: service_completed_successfully + networks: + - internal_network_a + volumes: + - type: bind + source: ./.config/a.test.default.yml + target: /misskey/.config/default.yml + read_only: true + + db.a.test: + extends: + file: ./compose.tpl.yml + service: db + networks: + - internal_network_a + volumes: + - type: bind + source: ./volumes/db.a + target: /var/lib/postgresql/data + bind: + create_host_path: true + +networks: + internal_network_a: + internal: true + driver: bridge + ipam: + config: + - subnet: 172.21.0.0/16 + ip_range: 172.21.0.0/24 diff --git a/packages/backend/test-federation/compose.b.yml b/packages/backend/test-federation/compose.b.yml new file mode 100644 index 0000000000..1158b53bae --- /dev/null +++ b/packages/backend/test-federation/compose.b.yml @@ -0,0 +1,64 @@ +services: + b.test: + extends: + file: ./compose.tpl.yml + service: nginx + depends_on: + misskey.b.test: + condition: service_healthy + networks: + - internal_network_b + volumes: + - type: bind + source: ./.config/b.test.conf + target: /etc/nginx/conf.d/b.test.conf + read_only: true + - type: bind + source: ./certificates/b.test.crt + target: /etc/nginx/certificates/b.test.crt + read_only: true + - type: bind + source: ./certificates/b.test.key + target: /etc/nginx/certificates/b.test.key + read_only: true + + misskey.b.test: + extends: + file: ./compose.tpl.yml + service: misskey + depends_on: + db.b.test: + condition: service_healthy + redis.test: + condition: service_healthy + setup: + condition: service_completed_successfully + networks: + - internal_network_b + volumes: + - type: bind + source: ./.config/b.test.default.yml + target: /misskey/.config/default.yml + read_only: true + + db.b.test: + extends: + file: ./compose.tpl.yml + service: db + networks: + - internal_network_b + volumes: + - type: bind + source: ./volumes/db.b + target: /var/lib/postgresql/data + bind: + create_host_path: true + +networks: + internal_network_b: + internal: true + driver: bridge + ipam: + config: + - subnet: 172.22.0.0/16 + ip_range: 172.22.0.0/24 diff --git a/packages/backend/test-federation/compose.override.yaml b/packages/backend/test-federation/compose.override.yaml new file mode 100644 index 0000000000..60a7631ab5 --- /dev/null +++ b/packages/backend/test-federation/compose.override.yaml @@ -0,0 +1,117 @@ +services: + setup: + volumes: + - type: volume + source: node_modules + target: /misskey/node_modules + - type: volume + source: node_modules_backend + target: /misskey/packages/backend/node_modules + - type: volume + source: node_modules_misskey-js + target: /misskey/packages/misskey-js/node_modules + - type: volume + source: node_modules_misskey-reversi + target: /misskey/packages/misskey-reversi/node_modules + + tester: + networks: + external_network: + internal_network: + ipv4_address: 172.20.1.1 + volumes: + - type: volume + source: node_modules_dev + target: /misskey/node_modules + - type: volume + source: node_modules_backend_dev + target: /misskey/packages/backend/node_modules + - type: volume + source: node_modules_misskey-js_dev + target: /misskey/packages/misskey-js/node_modules + + daemon: + networks: + - external_network + - internal_network_a + - internal_network_b + volumes: + - type: volume + source: node_modules_dev + target: /misskey/node_modules + - type: volume + source: node_modules_backend_dev + target: /misskey/packages/backend/node_modules + + redis.test: + networks: + - internal_network_a + - internal_network_b + + a.test: + networks: + - internal_network + + misskey.a.test: + networks: + - external_network + - internal_network + volumes: + - type: volume + source: node_modules + target: /misskey/node_modules + - type: volume + source: node_modules_backend + target: /misskey/packages/backend/node_modules + - type: volume + source: node_modules_misskey-js + target: /misskey/packages/misskey-js/node_modules + - type: volume + source: node_modules_misskey-reversi + target: /misskey/packages/misskey-reversi/node_modules + + b.test: + networks: + - internal_network + + misskey.b.test: + networks: + - external_network + - internal_network + volumes: + - type: volume + source: node_modules + target: /misskey/node_modules + - type: volume + source: node_modules_backend + target: /misskey/packages/backend/node_modules + - type: volume + source: node_modules_misskey-js + target: /misskey/packages/misskey-js/node_modules + - type: volume + source: node_modules_misskey-reversi + target: /misskey/packages/misskey-reversi/node_modules + +networks: + external_network: + driver: bridge + ipam: + config: + - subnet: 172.23.0.0/16 + ip_range: 172.23.0.0/24 + internal_network: + internal: true + driver: bridge + ipam: + config: + - subnet: 172.20.0.0/16 + ip_range: 172.20.0.0/24 + +volumes: + node_modules: + node_modules_dev: + node_modules_backend: + node_modules_backend_dev: + node_modules_misskey-js: + node_modules_misskey-js_dev: + node_modules_misskey-reversi: diff --git a/packages/backend/test-federation/compose.tpl.yml b/packages/backend/test-federation/compose.tpl.yml new file mode 100644 index 0000000000..8c38f16919 --- /dev/null +++ b/packages/backend/test-federation/compose.tpl.yml @@ -0,0 +1,101 @@ +services: + nginx: + image: nginx:1.27 + volumes: + - type: bind + source: ./certificates/rootCA.crt + target: /etc/nginx/certificates/rootCA.crt + read_only: true + healthcheck: + test: service nginx status + interval: 5s + retries: 20 + + misskey: + image: node:20 + env_file: + - ./.config/docker.env + environment: + - NODE_ENV=production + volumes: + - type: bind + source: ../../../built + target: /misskey/built + read_only: true + - type: bind + source: ../assets + target: /misskey/packages/backend/assets + read_only: true + - type: bind + source: ../built + target: /misskey/packages/backend/built + read_only: true + - type: bind + source: ../migration + target: /misskey/packages/backend/migration + read_only: true + - type: bind + source: ../ormconfig.js + target: /misskey/packages/backend/ormconfig.js + read_only: true + - type: bind + source: ../package.json + target: /misskey/packages/backend/package.json + read_only: true + - type: bind + source: ../../misskey-js/built + target: /misskey/packages/misskey-js/built + read_only: true + - type: bind + source: ../../misskey-js/package.json + target: /misskey/packages/misskey-js/package.json + read_only: true + - type: bind + source: ../../misskey-reversi/built + target: /misskey/packages/misskey-reversi/built + read_only: true + - type: bind + source: ../../misskey-reversi/package.json + target: /misskey/packages/misskey-reversi/package.json + read_only: true + - type: bind + source: ../../../healthcheck.sh + target: /misskey/healthcheck.sh + read_only: true + - type: bind + source: ../../../package.json + target: /misskey/package.json + read_only: true + - type: bind + source: ../../../pnpm-lock.yaml + target: /misskey/pnpm-lock.yaml + read_only: true + - type: bind + source: ../../../pnpm-workspace.yaml + target: /misskey/pnpm-workspace.yaml + read_only: true + - type: bind + source: ./certificates/rootCA.crt + target: /usr/local/share/ca-certificates/rootCA.crt + read_only: true + working_dir: /misskey + command: > + bash -c " + corepack enable && corepack prepare + pnpm -F backend migrate + pnpm -F backend start + " + healthcheck: + test: bash /misskey/healthcheck.sh + interval: 5s + retries: 20 + + db: + image: postgres:15-alpine + env_file: + - ./.config/docker.env + volumes: + healthcheck: + test: pg_isready -U $$POSTGRES_USER -d $$POSTGRES_DB + interval: 5s + retries: 20 diff --git a/packages/backend/test-federation/compose.yml b/packages/backend/test-federation/compose.yml new file mode 100644 index 0000000000..62d7e977c0 --- /dev/null +++ b/packages/backend/test-federation/compose.yml @@ -0,0 +1,133 @@ +include: + - ./compose.a.yml + - ./compose.b.yml + +services: + setup: + extends: + file: ./compose.tpl.yml + service: misskey + command: > + bash -c " + corepack enable && corepack prepare + pnpm -F backend i + pnpm -F misskey-js i + pnpm -F misskey-reversi i + " + + tester: + image: node:20 + depends_on: + a.test: + condition: service_healthy + b.test: + condition: service_healthy + environment: + - NODE_ENV=development + - NODE_EXTRA_CA_CERTS=/usr/local/share/ca-certificates/rootCA.crt + volumes: + - type: bind + source: ../package.json + target: /misskey/packages/backend/package.json + read_only: true + - type: bind + source: ../test/resources + target: /misskey/packages/backend/test/resources + read_only: true + - type: bind + source: ./test + target: /misskey/packages/backend/test-federation/test + read_only: true + - type: bind + source: ../jest.config.cjs + target: /misskey/packages/backend/jest.config.cjs + read_only: true + - type: bind + source: ../jest.config.fed.cjs + target: /misskey/packages/backend/jest.config.fed.cjs + read_only: true + - type: bind + source: ../../misskey-js/built + target: /misskey/packages/misskey-js/built + read_only: true + - type: bind + source: ../../misskey-js/package.json + target: /misskey/packages/misskey-js/package.json + read_only: true + - type: bind + source: ../../../package.json + target: /misskey/package.json + read_only: true + - type: bind + source: ../../../pnpm-lock.yaml + target: /misskey/pnpm-lock.yaml + read_only: true + - type: bind + source: ../../../pnpm-workspace.yaml + target: /misskey/pnpm-workspace.yaml + read_only: true + - type: bind + source: ./certificates/rootCA.crt + target: /usr/local/share/ca-certificates/rootCA.crt + read_only: true + working_dir: /misskey + entrypoint: > + bash -c ' + corepack enable && corepack prepare + pnpm -F misskey-js i --frozen-lockfile + pnpm -F backend i --frozen-lockfile + exec "$0" "$@" + ' + command: pnpm -F backend test:fed + + daemon: + image: node:20 + depends_on: + redis.test: + condition: service_healthy + volumes: + - type: bind + source: ../package.json + target: /misskey/packages/backend/package.json + read_only: true + - type: bind + source: ./daemon.ts + target: /misskey/packages/backend/test-federation/daemon.ts + read_only: true + - type: bind + source: ./tsconfig.json + target: /misskey/packages/backend/test-federation/tsconfig.json + read_only: true + - type: bind + source: ../../../package.json + target: /misskey/package.json + read_only: true + - type: bind + source: ../../../pnpm-lock.yaml + target: /misskey/pnpm-lock.yaml + read_only: true + - type: bind + source: ../../../pnpm-workspace.yaml + target: /misskey/pnpm-workspace.yaml + read_only: true + working_dir: /misskey + command: > + bash -c " + corepack enable && corepack prepare + pnpm -F backend i --frozen-lockfile + pnpm exec tsc -p ./packages/backend/test-federation + node ./packages/backend/test-federation/built/daemon.js + " + + redis.test: + image: redis:7-alpine + volumes: + - type: bind + source: ./volumes/redis + target: /data + bind: + create_host_path: true + healthcheck: + test: redis-cli ping + interval: 5s + retries: 20 diff --git a/packages/backend/test-federation/daemon.ts b/packages/backend/test-federation/daemon.ts new file mode 100644 index 0000000000..46b6963c79 --- /dev/null +++ b/packages/backend/test-federation/daemon.ts @@ -0,0 +1,38 @@ +import IPCIDR from 'ip-cidr'; +import { Redis } from 'ioredis'; + +const TESTER_IP_ADDRESS = '172.20.1.1'; + +/** + * This should be same as {@link file://./../src/misc/get-ip-hash.ts}. + */ +function getIpHash(ip: string) { + const prefix = IPCIDR.createAddress(ip).mask(64); + return `ip-${BigInt('0b' + prefix).toString(36)}`; +} + +/** + * This prevents hitting rate limit when login. + */ +export async function purgeLimit(host: string, client: Redis) { + const ipHash = getIpHash(TESTER_IP_ADDRESS); + const key = `${host}:limit:${ipHash}:signin`; + const res = await client.zrange(key, 0, -1); + if (res.length !== 0) { + console.log(`${key} - ${JSON.stringify(res)}`); + await client.del(key); + } +} + +console.log('Daemon started running'); + +{ + const redisClient = new Redis({ + host: 'redis.test', + }); + + setInterval(() => { + purgeLimit('a.test', redisClient); + purgeLimit('b.test', redisClient); + }, 200); +} diff --git a/packages/backend/test-federation/eslint.config.js b/packages/backend/test-federation/eslint.config.js new file mode 100644 index 0000000000..e3bcf4c0fe --- /dev/null +++ b/packages/backend/test-federation/eslint.config.js @@ -0,0 +1,21 @@ +import globals from 'globals'; +import tsParser from '@typescript-eslint/parser'; +import sharedConfig from '../../shared/eslint.config.js'; + +export default [ + ...sharedConfig, + { + files: ['**/*.ts', '**/*.tsx'], + languageOptions: { + globals: { + ...globals.node, + }, + parserOptions: { + parser: tsParser, + project: ['./tsconfig.json'], + sourceType: 'module', + tsconfigRootDir: import.meta.dirname, + }, + }, + }, +]; diff --git a/packages/backend/test-federation/setup.sh b/packages/backend/test-federation/setup.sh new file mode 100644 index 0000000000..1bc3a2a87c --- /dev/null +++ b/packages/backend/test-federation/setup.sh @@ -0,0 +1,35 @@ +#!/bin/bash +mkdir certificates + +# rootCA +openssl genrsa -des3 \ + -passout pass:rootCA \ + -out certificates/rootCA.key 4096 +openssl req -x509 -new -nodes -batch \ + -key certificates/rootCA.key \ + -sha256 \ + -days 1024 \ + -passin pass:rootCA \ + -out certificates/rootCA.crt + +# domain +function generate { + openssl req -new -newkey rsa:2048 -sha256 -nodes \ + -keyout certificates/$1.key \ + -subj "/CN=$1/emailAddress=admin@$1/C=JP/ST=/L=/O=Misskey Tester/OU=Some Unit" \ + -out certificates/$1.csr + openssl x509 -req -sha256 \ + -in certificates/$1.csr \ + -CA certificates/rootCA.crt \ + -CAkey certificates/rootCA.key \ + -CAcreateserial \ + -passin pass:rootCA \ + -out certificates/$1.crt \ + -days 500 + if [ ! -f .config/docker.env ]; then cp .config/example.docker.env .config/docker.env; fi + if [ ! -f .config/$1.conf ]; then sed "s/\${HOST}/$1/g" .config/example.conf > .config/$1.conf; fi + if [ ! -f .config/$1.default.yml ]; then sed "s/\${HOST}/$1/g" .config/example.default.yml > .config/$1.default.yml; fi +} + +generate a.test +generate b.test diff --git a/packages/backend/test-federation/test/abuse-report.test.ts b/packages/backend/test-federation/test/abuse-report.test.ts new file mode 100644 index 0000000000..b54d6222b4 --- /dev/null +++ b/packages/backend/test-federation/test/abuse-report.test.ts @@ -0,0 +1,52 @@ +import { rejects, strictEqual } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { createAccount, createModerator, resolveRemoteUser, sleep, type LoginUser } from './utils.js'; + +describe('Abuse report', () => { + describe('Forwarding report', () => { + let alice: LoginUser, bob: LoginUser, aModerator: LoginUser, bModerator: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [aModerator, bModerator] = await Promise.all([ + createModerator('a.test'), + createModerator('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Alice reports Bob, moderator in A forwards it, and B moderator receives it', async () => { + const comment = crypto.randomUUID(); + await alice.client.request('users/report-abuse', { userId: bobInA.id, comment }); + const reports = await aModerator.client.request('admin/abuse-user-reports', {}); + const report = reports.filter(report => report.comment === comment)[0]; + await aModerator.client.request('admin/forward-abuse-user-report', { reportId: report.id }); + await sleep(); + + const reportsInB = await bModerator.client.request('admin/abuse-user-reports', {}); + const reportInB = reportsInB.filter(report => report.comment.includes(comment))[0]; + // NOTE: reporter is not Alice, and is not moderator in A + strictEqual(reportInB.reporter.url, 'https://a.test/@instance.actor'); + strictEqual(reportInB.targetUserId, bob.id); + + // NOTE: cannot forward multiple times + await rejects( + async () => await aModerator.client.request('admin/forward-abuse-user-report', { reportId: report.id }), + (err: any) => { + strictEqual(err.code, 'INTERNAL_ERROR'); + strictEqual(err.info.e.message, 'The report has already been forwarded.'); + return true; + }, + ); + }); + }); +}); diff --git a/packages/backend/test-federation/test/block.test.ts b/packages/backend/test-federation/test/block.test.ts new file mode 100644 index 0000000000..ef910eeaea --- /dev/null +++ b/packages/backend/test-federation/test/block.test.ts @@ -0,0 +1,224 @@ +import { deepStrictEqual, rejects, strictEqual } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { assertNotificationReceived, createAccount, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js'; + +describe('Block', () => { + describe('Check follow', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Cannot follow if blocked', async () => { + await alice.client.request('blocking/create', { userId: bobInA.id }); + await sleep(); + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'BLOCKED'); + return true; + }, + ); + + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 0); + }); + + // FIXME: this is invalid case + test('Cannot follow even if unblocked', async () => { + // unblock here + await alice.client.request('blocking/delete', { userId: bobInA.id }); + await sleep(); + + // TODO: why still being blocked? + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'BLOCKED'); + return true; + }, + ); + }); + + test.skip('Can follow if unblocked', async () => { + await alice.client.request('blocking/delete', { userId: bobInA.id }); + await sleep(); + + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 1); + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); + }); + + test.skip('Remove follower when block them', async () => { + test('before block', async () => { + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 1); + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); + }); + + await alice.client.request('blocking/create', { userId: bobInA.id }); + await sleep(); + + test('after block', async () => { + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 0); + }); + }); + }); + + describe('Check reply', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Cannot reply if blocked', async () => { + await alice.client.request('blocking/create', { userId: bobInA.id }); + await sleep(); + + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + await rejects( + async () => await bob.client.request('notes/create', { text: 'b', replyId: resolvedNote.id }), + (err: any) => { + strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED'); + return true; + }, + ); + }); + + test('Can reply if unblocked', async () => { + await alice.client.request('blocking/delete', { userId: bobInA.id }); + await sleep(); + + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + const reply = (await bob.client.request('notes/create', { text: 'b', replyId: resolvedNote.id })).createdNote; + + await resolveRemoteNote('b.test', reply.id, alice); + }); + }); + + describe('Check reaction', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Cannot reaction if blocked', async () => { + await alice.client.request('blocking/create', { userId: bobInA.id }); + await sleep(); + + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + await rejects( + async () => await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' }), + (err: any) => { + strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED'); + return true; + }, + ); + }); + + // FIXME: this is invalid case + test('Cannot reaction even if unblocked', async () => { + // unblock here + await alice.client.request('blocking/delete', { userId: bobInA.id }); + await sleep(); + + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + + // TODO: why still being blocked? + await rejects( + async () => await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' }), + (err: any) => { + strictEqual(err.code, 'YOU_HAVE_BEEN_BLOCKED'); + return true; + }, + ); + }); + + test.skip('Can reaction if unblocked', async () => { + await alice.client.request('blocking/delete', { userId: bobInA.id }); + await sleep(); + + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: '😅' }); + + const _note = await alice.client.request('notes/show', { noteId: note.id }); + deepStrictEqual(_note.reactions, { '😅': 1 }); + }); + }); + + describe('Check mention', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + /** NOTE: You should mute the target to stop receiving notifications */ + test('Can mention and notified even if blocked', async () => { + await alice.client.request('blocking/create', { userId: bobInA.id }); + await sleep(); + + const text = `@${alice.username}@a.test plz unblock me!`; + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/create', { text }), + notification => notification.type === 'mention' && notification.userId === bobInA.id && notification.note.text === text, + true, + ); + }); + }); +}); diff --git a/packages/backend/test-federation/test/drive.test.ts b/packages/backend/test-federation/test/drive.test.ts new file mode 100644 index 0000000000..f755183b4d --- /dev/null +++ b/packages/backend/test-federation/test/drive.test.ts @@ -0,0 +1,175 @@ +import assert, { strictEqual } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { createAccount, deepStrictEqualWithExcludedFields, fetchAdmin, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep, uploadFile } from './utils.js'; + +const bAdmin = await fetchAdmin('b.test'); + +describe('Drive', () => { + describe('Upload image in a.test and resolve from b.test', () => { + let uploader: LoginUser; + + beforeAll(async () => { + uploader = await createAccount('a.test'); + }); + + let image: Misskey.entities.DriveFile, imageInB: Misskey.entities.DriveFile; + + describe('Upload', () => { + beforeAll(async () => { + image = await uploadFile('a.test', uploader); + const noteWithImage = (await uploader.client.request('notes/create', { fileIds: [image.id] })).createdNote; + const noteInB = await resolveRemoteNote('a.test', noteWithImage.id, bAdmin); + assert(noteInB.files != null); + strictEqual(noteInB.files.length, 1); + imageInB = noteInB.files[0]; + }); + + test('Check consistency of DriveFile', () => { + // console.log(`a.test: ${JSON.stringify(image, null, '\t')}`); + // console.log(`b.test: ${JSON.stringify(imageInB, null, '\t')}`); + + deepStrictEqualWithExcludedFields(image, imageInB, [ + 'id', + 'createdAt', + 'size', + 'url', + 'thumbnailUrl', + 'userId', + ]); + }); + }); + + let updatedImage: Misskey.entities.DriveFile, updatedImageInB: Misskey.entities.DriveFile; + + describe('Update', () => { + beforeAll(async () => { + updatedImage = await uploader.client.request('drive/files/update', { + fileId: image.id, + name: 'updated_192.jpg', + isSensitive: true, + }); + + updatedImageInB = await bAdmin.client.request('drive/files/show', { + fileId: imageInB.id, + }); + }); + + test('Check consistency', () => { + // console.log(`a.test: ${JSON.stringify(updatedImage, null, '\t')}`); + // console.log(`b.test: ${JSON.stringify(updatedImageInB, null, '\t')}`); + + // FIXME: not updated with `drive/files/update` + strictEqual(updatedImage.isSensitive, true); + strictEqual(updatedImage.name, 'updated_192.jpg'); + strictEqual(updatedImageInB.isSensitive, false); + strictEqual(updatedImageInB.name, '192.jpg'); + }); + }); + + let reupdatedImageInB: Misskey.entities.DriveFile; + + describe('Re-update with attaching to Note', () => { + beforeAll(async () => { + const noteWithUpdatedImage = (await uploader.client.request('notes/create', { fileIds: [updatedImage.id] })).createdNote; + const noteWithUpdatedImageInB = await resolveRemoteNote('a.test', noteWithUpdatedImage.id, bAdmin); + assert(noteWithUpdatedImageInB.files != null); + strictEqual(noteWithUpdatedImageInB.files.length, 1); + reupdatedImageInB = noteWithUpdatedImageInB.files[0]; + }); + + test('Check consistency', () => { + // console.log(`b.test: ${JSON.stringify(reupdatedImageInB, null, '\t')}`); + + // `isSensitive` is updated + strictEqual(reupdatedImageInB.isSensitive, true); + // FIXME: but `name` is not updated + strictEqual(reupdatedImageInB.name, '192.jpg'); + }); + }); + }); + + describe('Sensitive flag', () => { + describe('isSensitive is federated in delivering to followers', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + }); + + test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => { + const file = await uploadFile('a.test', alice); + await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true }); + await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id] }); + await sleep(); + + const notes = await bob.client.request('notes/timeline', {}); + strictEqual(notes.length, 1); + const noteInB = notes[0]; + assert(noteInB.files != null); + strictEqual(noteInB.files.length, 1); + strictEqual(noteInB.files[0].isSensitive, true); + }); + }); + + describe('isSensitive is federated in resolving', () => { + let alice: LoginUser, bob: LoginUser; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + }); + + test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => { + const file = await uploadFile('a.test', alice); + await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true }); + const note = (await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id] })).createdNote; + + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + assert(noteInB.files != null); + strictEqual(noteInB.files.length, 1); + strictEqual(noteInB.files[0].isSensitive, true); + }); + }); + + /** @see https://github.com/misskey-dev/misskey/issues/12208 */ + describe('isSensitive is federated in replying', () => { + let alice: LoginUser, bob: LoginUser; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + }); + + test('Alice uploads sensitive image and it is shown as sensitive from Bob', async () => { + const bobNote = (await bob.client.request('notes/create', { text: 'I\'m Bob' })).createdNote; + + const file = await uploadFile('a.test', alice); + await alice.client.request('drive/files/update', { fileId: file.id, isSensitive: true }); + const bobNoteInA = await resolveRemoteNote('b.test', bobNote.id, alice); + const note = (await alice.client.request('notes/create', { text: 'sensitive', fileIds: [file.id], replyId: bobNoteInA.id })).createdNote; + await sleep(); + + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + assert(noteInB.files != null); + strictEqual(noteInB.files.length, 1); + strictEqual(noteInB.files[0].isSensitive, true); + }); + }); + }); +}); diff --git a/packages/backend/test-federation/test/emoji.test.ts b/packages/backend/test-federation/test/emoji.test.ts new file mode 100644 index 0000000000..3119ca6e4d --- /dev/null +++ b/packages/backend/test-federation/test/emoji.test.ts @@ -0,0 +1,97 @@ +import assert, { deepStrictEqual, strictEqual } from 'assert'; +import * as Misskey from 'misskey-js'; +import { addCustomEmoji, createAccount, type LoginUser, resolveRemoteUser, sleep } from './utils.js'; + +describe('Emoji', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + }); + + test('Custom emoji are delivered with Note delivery', async () => { + const emoji = await addCustomEmoji('a.test'); + await alice.client.request('notes/create', { text: `I love :${emoji.name}:` }); + await sleep(); + + const notes = await bob.client.request('notes/timeline', {}); + const noteInB = notes[0]; + + strictEqual(noteInB.text, `I love \u200b:${emoji.name}:\u200b`); + assert(noteInB.emojis != null); + assert(emoji.name in noteInB.emojis); + strictEqual(noteInB.emojis[emoji.name], emoji.url); + }); + + test('Custom emoji are delivered with Reaction delivery', async () => { + const emoji = await addCustomEmoji('a.test'); + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + await sleep(); + + await alice.client.request('notes/reactions/create', { noteId: note.id, reaction: `:${emoji.name}:` }); + await sleep(); + + const noteInB = (await bob.client.request('notes/timeline', {}))[0]; + deepStrictEqual(noteInB.reactions[`:${emoji.name}@a.test:`], 1); + deepStrictEqual(noteInB.reactionEmojis[`${emoji.name}@a.test`], emoji.url); + }); + + test('Custom emoji are delivered with Profile delivery', async () => { + const emoji = await addCustomEmoji('a.test'); + const renewedAlice = await alice.client.request('i/update', { name: `:${emoji.name}:` }); + await sleep(); + + const renewedaliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(renewedaliceInB.name, renewedAlice.name); + assert(emoji.name in renewedaliceInB.emojis); + strictEqual(renewedaliceInB.emojis[emoji.name], emoji.url); + }); + + test('Local-only custom emoji aren\'t delivered with Note delivery', async () => { + const emoji = await addCustomEmoji('a.test', { localOnly: true }); + await alice.client.request('notes/create', { text: `I love :${emoji.name}:` }); + await sleep(); + + const notes = await bob.client.request('notes/timeline', {}); + const noteInB = notes[0]; + + strictEqual(noteInB.text, `I love \u200b:${emoji.name}:\u200b`); + // deepStrictEqual(noteInB.emojis, {}); // TODO: this fails (why?) + deepStrictEqual({ ...noteInB.emojis }, {}); + }); + + test('Local-only custom emoji aren\'t delivered with Reaction delivery', async () => { + const emoji = await addCustomEmoji('a.test', { localOnly: true }); + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + await sleep(); + + await alice.client.request('notes/reactions/create', { noteId: note.id, reaction: `:${emoji.name}:` }); + await sleep(); + + const noteInB = (await bob.client.request('notes/timeline', {}))[0]; + deepStrictEqual({ ...noteInB.reactions }, { 'â¤': 1 }); + deepStrictEqual({ ...noteInB.reactionEmojis }, {}); + }); + + test('Local-only custom emoji aren\'t delivered with Profile delivery', async () => { + const emoji = await addCustomEmoji('a.test', { localOnly: true }); + const renewedAlice = await alice.client.request('i/update', { name: `:${emoji.name}:` }); + await sleep(); + + const renewedaliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(renewedaliceInB.name, renewedAlice.name); + deepStrictEqual({ ...renewedaliceInB.emojis }, {}); + }); +}); diff --git a/packages/backend/test-federation/test/move.test.ts b/packages/backend/test-federation/test/move.test.ts new file mode 100644 index 0000000000..56a57de8a4 --- /dev/null +++ b/packages/backend/test-federation/test/move.test.ts @@ -0,0 +1,52 @@ +import assert, { strictEqual } from 'node:assert'; +import { createAccount, type LoginUser, sleep } from './utils.js'; + +describe('Move', () => { + test('Minimum move', async () => { + const [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + await bob.client.request('i/update', { alsoKnownAs: [`@${alice.username}@a.test`] }); + await alice.client.request('i/move', { moveToAccount: `@${bob.username}@b.test` }); + }); + + /** @see https://github.com/misskey-dev/misskey/issues/11320 */ + describe('Following relation is transferred after move', () => { + let alice: LoginUser, bob: LoginUser, carol: LoginUser; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + carol = await createAccount('a.test'); + + // Follow @carol@a.test ==> @alice@a.test + await carol.client.request('following/create', { userId: alice.id }); + + // Move @alice@a.test ==> @bob@b.test + await bob.client.request('i/update', { alsoKnownAs: [`@${alice.username}@a.test`] }); + await alice.client.request('i/move', { moveToAccount: `@${bob.username}@b.test` }); + await sleep(); + }); + + test('Check from follower', async () => { + const following = await carol.client.request('users/following', { userId: carol.id }); + strictEqual(following.length, 2); + const followees = following.map(({ followee }) => followee); + assert(followees.every(followee => followee != null)); + assert(followees.some(({ id, url }) => id === alice.id && url === null)); + assert(followees.some(({ url }) => url === `https://b.test/@${bob.username}`)); + }); + + test('Check from followee', async () => { + const followers = await bob.client.request('users/followers', { userId: bob.id }); + strictEqual(followers.length, 1); + const follower = followers[0].follower; + assert(follower != null); + strictEqual(follower.url, `https://a.test/@${carol.username}`); + }); + }); +}); diff --git a/packages/backend/test-federation/test/note.test.ts b/packages/backend/test-federation/test/note.test.ts new file mode 100644 index 0000000000..bacc4cc54f --- /dev/null +++ b/packages/backend/test-federation/test/note.test.ts @@ -0,0 +1,317 @@ +import assert, { rejects, strictEqual } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { addCustomEmoji, createAccount, createModerator, deepStrictEqualWithExcludedFields, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep, uploadFile } from './utils.js'; + +describe('Note', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + describe('Note content', () => { + test('Consistency of Public Note', async () => { + const image = await uploadFile('a.test', alice); + const note = (await alice.client.request('notes/create', { + text: 'I am Alice!', + fileIds: [image.id], + poll: { + choices: ['neko', 'inu'], + multiple: false, + expiredAfter: 60 * 60 * 1000, + }, + })).createdNote; + + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + deepStrictEqualWithExcludedFields(note, resolvedNote, [ + 'id', + 'emojis', + /** Consistency of files is checked at {@link file://./drive.test.ts}, so let's skip. */ + 'fileIds', + 'files', + /** @see https://github.com/misskey-dev/misskey/issues/12409 */ + 'reactionAcceptance', + 'userId', + 'user', + 'uri', + ]); + strictEqual(aliceInB.id, resolvedNote.userId); + }); + + test('Consistency of reply', async () => { + const _replyedNote = (await alice.client.request('notes/create', { + text: 'a', + })).createdNote; + const note = (await alice.client.request('notes/create', { + text: 'b', + replyId: _replyedNote.id, + })).createdNote; + // NOTE: the repliedCount is incremented, so fetch again + const replyedNote = await alice.client.request('notes/show', { noteId: _replyedNote.id }); + strictEqual(replyedNote.repliesCount, 1); + + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + deepStrictEqualWithExcludedFields(note, resolvedNote, [ + 'id', + 'emojis', + 'reactionAcceptance', + 'replyId', + 'reply', + 'userId', + 'user', + 'uri', + ]); + assert(resolvedNote.replyId != null); + assert(resolvedNote.reply != null); + deepStrictEqualWithExcludedFields(replyedNote, resolvedNote.reply, [ + 'id', + // TODO: why clippedCount loses consistency? + 'clippedCount', + 'emojis', + 'userId', + 'user', + 'uri', + // flaky because this is parallelly incremented, so let's check it below + 'repliesCount', + ]); + strictEqual(aliceInB.id, resolvedNote.userId); + + await sleep(); + + const resolvedReplyedNote = await bob.client.request('notes/show', { noteId: resolvedNote.replyId }); + strictEqual(resolvedReplyedNote.repliesCount, 1); + }); + + test('Consistency of Renote', async () => { + // NOTE: the renoteCount is not incremented, so no need to fetch again + const renotedNote = (await alice.client.request('notes/create', { + text: 'a', + })).createdNote; + const note = (await alice.client.request('notes/create', { + text: 'b', + renoteId: renotedNote.id, + })).createdNote; + + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + deepStrictEqualWithExcludedFields(note, resolvedNote, [ + 'id', + 'emojis', + 'reactionAcceptance', + 'renoteId', + 'renote', + 'userId', + 'user', + 'uri', + ]); + assert(resolvedNote.renoteId != null); + assert(resolvedNote.renote != null); + deepStrictEqualWithExcludedFields(renotedNote, resolvedNote.renote, [ + 'id', + 'emojis', + 'userId', + 'user', + 'uri', + ]); + strictEqual(aliceInB.id, resolvedNote.userId); + }); + }); + + describe('Other props', () => { + test('localOnly', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', localOnly: true })).createdNote; + rejects( + async () => await bob.client.request('ap/show', { uri: `https://a.test/notes/${note.id}` }), + (err: any) => { + /** + * FIXME: this error is not handled + * @see https://github.com/misskey-dev/misskey/issues/12736 + */ + strictEqual(err.code, 'INTERNAL_ERROR'); + return true; + }, + ); + }); + }); + + describe('Deletion', () => { + describe('Check Delete consistency', () => { + let carol: LoginUser; + + beforeAll(async () => { + carol = await createAccount('a.test'); + + await carol.client.request('following/create', { userId: bobInA.id }); + await sleep(); + }); + + test('Delete is derivered to followers', async () => { + const note = (await bob.client.request('notes/create', { text: 'I\'m Bob.' })).createdNote; + const noteInA = await resolveRemoteNote('b.test', note.id, carol); + await bob.client.request('notes/delete', { noteId: note.id }); + await sleep(); + + await rejects( + async () => await carol.client.request('notes/show', { noteId: noteInA.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_NOTE'); + return true; + }, + ); + }); + }); + + describe('Deletion of remote user\'s note for moderation', () => { + let note: Misskey.entities.Note; + + test('Alice post is deleted in B', async () => { + note = (await alice.client.request('notes/create', { text: 'Hello' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const bMod = await createModerator('b.test'); + await bMod.client.request('notes/delete', { noteId: noteInB.id }); + await rejects( + async () => await bob.client.request('notes/show', { noteId: noteInB.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_NOTE'); + return true; + }, + ); + }); + + /** + * FIXME: implement soft deletion as well as user? + * @see https://github.com/misskey-dev/misskey/issues/11437 + */ + test.failing('Not found even if resolve again', async () => { + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + await rejects( + async () => await bob.client.request('notes/show', { noteId: noteInB.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_NOTE'); + return true; + }, + ); + }); + }); + }); + + describe('Reaction', () => { + describe('Consistency', () => { + test('Unicode reaction', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + const reaction = '😅'; + await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction }); + await sleep(); + + const reactions = await alice.client.request('notes/reactions', { noteId: note.id }); + strictEqual(reactions.length, 1); + strictEqual(reactions[0].type, reaction); + strictEqual(reactions[0].user.id, bobInA.id); + }); + + test('Custom emoji reaction', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const resolvedNote = await resolveRemoteNote('a.test', note.id, bob); + const emoji = await addCustomEmoji('b.test'); + await bob.client.request('notes/reactions/create', { noteId: resolvedNote.id, reaction: `:${emoji.name}:` }); + await sleep(); + + const reactions = await alice.client.request('notes/reactions', { noteId: note.id }); + strictEqual(reactions.length, 1); + strictEqual(reactions[0].type, `:${emoji.name}@b.test:`); + strictEqual(reactions[0].user.id, bobInA.id); + }); + }); + + describe('Acceptance', () => { + test('Even if likeOnly, remote users can react with custom emoji, but it is converted to like', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', reactionAcceptance: 'likeOnly' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const emoji = await addCustomEmoji('b.test'); + await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction: `:${emoji.name}:` }); + await sleep(); + + const reactions = await alice.client.request('notes/reactions', { noteId: note.id }); + strictEqual(reactions.length, 1); + strictEqual(reactions[0].type, 'â¤'); + }); + + /** + * TODO: this may be unexpected behavior? + * @see https://github.com/misskey-dev/misskey/issues/12409 + */ + test('Even if nonSensitiveOnly, remote users can react with sensitive emoji, and it is not converted', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', reactionAcceptance: 'nonSensitiveOnly' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const emoji = await addCustomEmoji('b.test', { isSensitive: true }); + await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction: `:${emoji.name}:` }); + await sleep(); + + const reactions = await alice.client.request('notes/reactions', { noteId: note.id }); + strictEqual(reactions.length, 1); + strictEqual(reactions[0].type, `:${emoji.name}@b.test:`); + }); + }); + }); + + describe('Poll', () => { + describe('Any remote user\'s vote is delivered to the author', () => { + let carol: LoginUser; + + beforeAll(async () => { + carol = await createAccount('a.test'); + }); + + test('Bob creates poll and receives a vote from Carol', async () => { + const note = (await bob.client.request('notes/create', { poll: { choices: ['inu', 'neko'] } })).createdNote; + const noteInA = await resolveRemoteNote('b.test', note.id, carol); + await carol.client.request('notes/polls/vote', { noteId: noteInA.id, choice: 0 }); + await sleep(); + + const noteAfterVote = await bob.client.request('notes/show', { noteId: note.id }); + assert(noteAfterVote.poll != null); + strictEqual(noteAfterVote.poll.choices[0].votes, 1); + strictEqual(noteAfterVote.poll.choices[1].votes, 0); + }); + }); + + describe('Local user\'s vote is delivered to the author\'s remote followers', () => { + let bobRemoteFollower: LoginUser, localVoter: LoginUser; + + beforeAll(async () => { + [ + bobRemoteFollower, + localVoter, + ] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + await bobRemoteFollower.client.request('following/create', { userId: bobInA.id }); + await sleep(); + }); + + test('A vote in Bob\'s server is delivered to Bob\'s remote followers', async () => { + const note = (await bob.client.request('notes/create', { poll: { choices: ['inu', 'neko'] } })).createdNote; + // NOTE: resolve before voting + const noteInA = await resolveRemoteNote('b.test', note.id, bobRemoteFollower); + await localVoter.client.request('notes/polls/vote', { noteId: note.id, choice: 0 }); + await sleep(); + + const noteAfterVote = await bobRemoteFollower.client.request('notes/show', { noteId: noteInA.id }); + assert(noteAfterVote.poll != null); + strictEqual(noteAfterVote.poll.choices[0].votes, 1); + strictEqual(noteAfterVote.poll.choices[1].votes, 0); + }); + }); + }); +}); diff --git a/packages/backend/test-federation/test/notification.test.ts b/packages/backend/test-federation/test/notification.test.ts new file mode 100644 index 0000000000..6d55353653 --- /dev/null +++ b/packages/backend/test-federation/test/notification.test.ts @@ -0,0 +1,107 @@ +import * as Misskey from 'misskey-js'; +import { assertNotificationReceived, createAccount, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js'; + +describe('Notification', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + describe('Follow', () => { + test('Get notification when follow', async () => { + await assertNotificationReceived( + 'b.test', bob, + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + notification => notification.type === 'followRequestAccepted' && notification.userId === aliceInB.id, + true, + ); + + await bob.client.request('following/delete', { userId: aliceInB.id }); + await sleep(); + }); + + test('Get notification when get followed', async () => { + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + notification => notification.type === 'follow' && notification.userId === bobInA.id, + true, + ); + }); + + afterAll(async () => await bob.client.request('following/delete', { userId: aliceInB.id })); + }); + + describe('Note', () => { + test('Get notification when get a reaction', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const reaction = '😅'; + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/reactions/create', { noteId: noteInB.id, reaction }), + notification => + notification.type === 'reaction' && notification.note.id === note.id && notification.userId === bobInA.id && notification.reaction === reaction, + true, + ); + }); + + test('Get notification when replied', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const text = crypto.randomUUID(); + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/create', { text, replyId: noteInB.id }), + notification => + notification.type === 'reply' && notification.note.reply!.id === note.id && notification.userId === bobInA.id && notification.note.text === text, + true, + ); + }); + + test('Get notification when renoted', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/create', { renoteId: noteInB.id }), + notification => + notification.type === 'renote' && notification.note.renote!.id === note.id && notification.userId === bobInA.id, + true, + ); + }); + + test('Get notification when quoted', async () => { + const note = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + const noteInB = await resolveRemoteNote('a.test', note.id, bob); + const text = crypto.randomUUID(); + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/create', { text, renoteId: noteInB.id }), + notification => + notification.type === 'quote' && notification.note.renote!.id === note.id && notification.userId === bobInA.id && notification.note.text === text, + true, + ); + }); + + test('Get notification when mentioned', async () => { + const text = `@${alice.username}@a.test`; + await assertNotificationReceived( + 'a.test', alice, + async () => await bob.client.request('notes/create', { text }), + notification => notification.type === 'mention' && notification.userId === bobInA.id && notification.note.text === text, + true, + ); + }); + }); +}); diff --git a/packages/backend/test-federation/test/timeline.test.ts b/packages/backend/test-federation/test/timeline.test.ts new file mode 100644 index 0000000000..2250bf4a42 --- /dev/null +++ b/packages/backend/test-federation/test/timeline.test.ts @@ -0,0 +1,328 @@ +import { strictEqual } from 'assert'; +import * as Misskey from 'misskey-js'; +import { createAccount, fetchAdmin, isNoteUpdatedEventFired, isFired, type LoginUser, type Request, resolveRemoteUser, sleep, createRole } from './utils.js'; + +const bAdmin = await fetchAdmin('b.test'); + +describe('Timeline', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + }); + + type TimelineChannel = keyof Misskey.Channels & (`${string}Timeline` | 'antenna' | 'userList' | 'hashtag'); + type TimelineEndpoint = keyof Misskey.Endpoints & (`${string}timeline` | 'antennas/notes' | 'roles/notes' | 'notes/search-by-tag'); + const timelineMap = new Map<TimelineChannel, TimelineEndpoint>([ + ['antenna', 'antennas/notes'], + ['globalTimeline', 'notes/global-timeline'], + ['homeTimeline', 'notes/timeline'], + ['hybridTimeline', 'notes/hybrid-timeline'], + ['localTimeline', 'notes/local-timeline'], + ['roleTimeline', 'roles/notes'], + ['hashtag', 'notes/search-by-tag'], + ['userList', 'notes/user-list-timeline'], + ]); + + async function postAndCheckReception<C extends TimelineChannel>( + timelineChannel: C, + expect: boolean, + noteParams: Misskey.entities.NotesCreateRequest = {}, + channelParams: Misskey.Channels[C]['params'] = {}, + ) { + let note: Misskey.entities.Note | undefined; + const text = noteParams.text ?? crypto.randomUUID(); + const streamingFired = await isFired( + 'b.test', bob, timelineChannel, + async () => { + note = (await alice.client.request('notes/create', { text, ...noteParams })).createdNote; + }, + 'note', msg => msg.text === text, + channelParams, + ); + strictEqual(streamingFired, expect); + + const endpoint = timelineMap.get(timelineChannel)!; + const params: Misskey.Endpoints[typeof endpoint]['req'] = + endpoint === 'antennas/notes' ? { antennaId: (channelParams as Misskey.Channels['antenna']['params']).antennaId } : + endpoint === 'notes/user-list-timeline' ? { listId: (channelParams as Misskey.Channels['userList']['params']).listId } : + endpoint === 'notes/search-by-tag' ? { query: (channelParams as Misskey.Channels['hashtag']['params']).q } : + endpoint === 'roles/notes' ? { roleId: (channelParams as Misskey.Channels['roleTimeline']['params']).roleId } : + {}; + + await sleep(); + const notes = await (bob.client.request as Request)(endpoint, params); + const noteInB = notes.filter(({ uri }) => uri === `https://a.test/notes/${note!.id}`).pop(); + const endpointFired = noteInB != null; + strictEqual(endpointFired, expect); + + // Let's check Delete reception + if (expect) { + const streamingFired = await isNoteUpdatedEventFired( + 'b.test', bob, noteInB!.id, + async () => await alice.client.request('notes/delete', { noteId: note!.id }), + msg => msg.type === 'deleted' && msg.id === noteInB!.id, + ); + strictEqual(streamingFired, true); + + await sleep(); + const notes = await (bob.client.request as Request)(endpoint, params); + const endpointFired = notes.every(({ uri }) => uri !== `https://a.test/notes/${note!.id}`); + strictEqual(endpointFired, true); + } + } + + describe('homeTimeline', () => { + // NOTE: narrowing scope intentionally to prevent mistakes by copy-and-paste + const homeTimeline = 'homeTimeline'; + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(homeTimeline, true); + }); + + test('Receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(homeTimeline, true, { visibility: 'home' }); + }); + + test('Receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(homeTimeline, true, { visibility: 'followers' }); + }); + + test('Receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(homeTimeline, true, { visibility: 'specified', visibleUserIds: [bobInA.id] }); + }); + + test('Don\'t receive remote followee\'s localOnly Note', async () => { + await postAndCheckReception(homeTimeline, false, { localOnly: true }); + }); + + test('Don\'t receive remote followee\'s invisible specified-only Note', async () => { + await postAndCheckReception(homeTimeline, false, { visibility: 'specified' }); + }); + + /** + * FIXME: can receive this + * @see https://github.com/misskey-dev/misskey/issues/14083 + */ + test.failing('Don\'t receive remote followee\'s invisible and mentioned specified-only Note', async () => { + await postAndCheckReception(homeTimeline, false, { text: `@${bob.username}@b.test Hello`, visibility: 'specified' }); + }); + + /** + * FIXME: cannot receive this + * @see https://github.com/misskey-dev/misskey/issues/14084 + */ + test.failing('Receive remote followee\'s visible specified-only reply to invisible specified-only Note', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', visibility: 'specified' })).createdNote; + await postAndCheckReception(homeTimeline, true, { replyId: note.id, visibility: 'specified', visibleUserIds: [bobInA.id] }); + }); + }); + }); + + describe('localTimeline', () => { + const localTimeline = 'localTimeline'; + + describe('Check reception of remote followee\'s Note', () => { + test('Don\'t receive remote followee\'s Note', async () => { + await postAndCheckReception(localTimeline, false); + }); + }); + }); + + describe('hybridTimeline', () => { + const hybridTimeline = 'hybridTimeline'; + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(hybridTimeline, true); + }); + + test('Receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(hybridTimeline, true, { visibility: 'home' }); + }); + + test('Receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(hybridTimeline, true, { visibility: 'followers' }); + }); + + test('Receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(hybridTimeline, true, { visibility: 'specified', visibleUserIds: [bobInA.id] }); + }); + }); + }); + + describe('globalTimeline', () => { + const globalTimeline = 'globalTimeline'; + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(globalTimeline, true); + }); + + test('Don\'t receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(globalTimeline, false, { visibility: 'home' }); + }); + + test('Don\'t receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(globalTimeline, false, { visibility: 'followers' }); + }); + + test('Don\'t receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(globalTimeline, false, { visibility: 'specified', visibleUserIds: [bobInA.id] }); + }); + }); + }); + + describe('userList', () => { + const userList = 'userList'; + + let list: Misskey.entities.UserList; + + beforeAll(async () => { + list = await bob.client.request('users/lists/create', { name: 'Bob\'s List' }); + await bob.client.request('users/lists/push', { listId: list.id, userId: aliceInB.id }); + await sleep(); + }); + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(userList, true, {}, { listId: list.id }); + }); + + test('Receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(userList, true, { visibility: 'home' }, { listId: list.id }); + }); + + test('Receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(userList, true, { visibility: 'followers' }, { listId: list.id }); + }); + + test('Receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(userList, true, { visibility: 'specified', visibleUserIds: [bobInA.id] }, { listId: list.id }); + }); + }); + }); + + describe('hashtag', () => { + const hashtag = 'hashtag'; + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + const tag = crypto.randomUUID(); + await postAndCheckReception(hashtag, true, { text: `#${tag}` }, { q: [[tag]] }); + }); + + test('Receive remote followee\'s home-only Note', async () => { + const tag = crypto.randomUUID(); + await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'home' }, { q: [[tag]] }); + }); + + test('Receive remote followee\'s followers-only Note', async () => { + const tag = crypto.randomUUID(); + await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'followers' }, { q: [[tag]] }); + }); + + test('Receive remote followee\'s visible specified-only Note', async () => { + const tag = crypto.randomUUID(); + await postAndCheckReception(hashtag, true, { text: `#${tag}`, visibility: 'specified', visibleUserIds: [bobInA.id] }, { q: [[tag]] }); + }); + }); + }); + + describe('roleTimeline', () => { + const roleTimeline = 'roleTimeline'; + + let role: Misskey.entities.Role; + + beforeAll(async () => { + role = await createRole('b.test', { + name: 'Remote Users', + description: 'Remote users are assigned to this role.', + condFormula: { + /** TODO: @see https://github.com/misskey-dev/misskey/issues/14169 */ + type: 'isRemote' as never, + }, + }); + await sleep(); + }); + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(roleTimeline, true, {}, { roleId: role.id }); + }); + + test('Don\'t receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(roleTimeline, false, { visibility: 'home' }, { roleId: role.id }); + }); + + test('Don\'t receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(roleTimeline, false, { visibility: 'followers' }, { roleId: role.id }); + }); + + test('Don\'t receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(roleTimeline, false, { visibility: 'specified', visibleUserIds: [bobInA.id] }, { roleId: role.id }); + }); + }); + + afterAll(async () => { + await bAdmin.client.request('admin/roles/delete', { roleId: role.id }); + }); + }); + + // TODO: Cannot test + describe.skip('antenna', () => { + const antenna = 'antenna'; + + let bobAntenna: Misskey.entities.Antenna; + + beforeAll(async () => { + bobAntenna = await bob.client.request('antennas/create', { + name: 'Bob\'s Egosurfing Antenna', + src: 'all', + keywords: [['Bob']], + excludeKeywords: [], + users: [], + caseSensitive: false, + localOnly: false, + withReplies: true, + withFile: true, + }); + await sleep(); + }); + + describe('Check reception of remote followee\'s Note', () => { + test('Receive remote followee\'s Note', async () => { + await postAndCheckReception(antenna, true, { text: 'I love Bob (1)' }, { antennaId: bobAntenna.id }); + }); + + test('Don\'t receive remote followee\'s home-only Note', async () => { + await postAndCheckReception(antenna, false, { text: 'I love Bob (2)', visibility: 'home' }, { antennaId: bobAntenna.id }); + }); + + test('Don\'t receive remote followee\'s followers-only Note', async () => { + await postAndCheckReception(antenna, false, { text: 'I love Bob (3)', visibility: 'followers' }, { antennaId: bobAntenna.id }); + }); + + test('Don\'t receive remote followee\'s visible specified-only Note', async () => { + await postAndCheckReception(antenna, false, { text: 'I love Bob (4)', visibility: 'specified', visibleUserIds: [bobInA.id] }, { antennaId: bobAntenna.id }); + }); + }); + + afterAll(async () => { + await bob.client.request('antennas/delete', { antennaId: bobAntenna.id }); + }); + }); +}); diff --git a/packages/backend/test-federation/test/user.test.ts b/packages/backend/test-federation/test/user.test.ts new file mode 100644 index 0000000000..76605e61d4 --- /dev/null +++ b/packages/backend/test-federation/test/user.test.ts @@ -0,0 +1,560 @@ +import assert, { rejects, strictEqual } from 'node:assert'; +import * as Misskey from 'misskey-js'; +import { createAccount, deepStrictEqualWithExcludedFields, fetchAdmin, type LoginUser, resolveRemoteNote, resolveRemoteUser, sleep } from './utils.js'; + +const [aAdmin, bAdmin] = await Promise.all([ + fetchAdmin('a.test'), + fetchAdmin('b.test'), +]); + +describe('User', () => { + describe('Profile', () => { + describe('Consistency of profile', () => { + let alice: LoginUser; + let aliceWatcher: LoginUser; + let aliceWatcherInB: LoginUser; + + beforeAll(async () => { + alice = await createAccount('a.test'); + [ + aliceWatcher, + aliceWatcherInB, + ] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + }); + + test('Check consistency', async () => { + const aliceInA = await aliceWatcher.client.request('users/show', { userId: alice.id }); + const resolved = await resolveRemoteUser('a.test', aliceInA.id, aliceWatcherInB); + const aliceInB = await aliceWatcherInB.client.request('users/show', { userId: resolved.id }); + + // console.log(`a.test: ${JSON.stringify(aliceInA, null, '\t')}`); + // console.log(`b.test: ${JSON.stringify(aliceInB, null, '\t')}`); + + deepStrictEqualWithExcludedFields(aliceInA, aliceInB, [ + 'id', + 'host', + 'avatarUrl', + 'instance', + 'badgeRoles', + 'url', + 'uri', + 'createdAt', + 'lastFetchedAt', + 'publicReactions', + ]); + }); + }); + + describe('ffVisibility is federated', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + + // NOTE: follow each other + await Promise.all([ + alice.client.request('following/create', { userId: bobInA.id }), + bob.client.request('following/create', { userId: aliceInB.id }), + ]); + await sleep(); + }); + + test('Visibility set public by default', async () => { + for (const user of await Promise.all([ + alice.client.request('users/show', { userId: bobInA.id }), + bob.client.request('users/show', { userId: aliceInB.id }), + ])) { + strictEqual(user.followersVisibility, 'public'); + strictEqual(user.followingVisibility, 'public'); + } + }); + + /** FIXME: not working */ + test.skip('Setting private for followersVisibility is federated', async () => { + await Promise.all([ + alice.client.request('i/update', { followersVisibility: 'private' }), + bob.client.request('i/update', { followersVisibility: 'private' }), + ]); + await sleep(); + + for (const user of await Promise.all([ + alice.client.request('users/show', { userId: bobInA.id }), + bob.client.request('users/show', { userId: aliceInB.id }), + ])) { + strictEqual(user.followersVisibility, 'private'); + strictEqual(user.followingVisibility, 'public'); + } + }); + + test.skip('Setting private for followingVisibility is federated', async () => { + await Promise.all([ + alice.client.request('i/update', { followingVisibility: 'private' }), + bob.client.request('i/update', { followingVisibility: 'private' }), + ]); + await sleep(); + + for (const user of await Promise.all([ + alice.client.request('users/show', { userId: bobInA.id }), + bob.client.request('users/show', { userId: aliceInB.id }), + ])) { + strictEqual(user.followersVisibility, 'private'); + strictEqual(user.followingVisibility, 'private'); + } + }); + }); + + describe('isCat is federated', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Not isCat for default', () => { + strictEqual(aliceInB.isCat, false); + }); + + test('Becoming a cat is sent to their followers', async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + await alice.client.request('i/update', { isCat: true }); + await sleep(); + + const res = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(res.isCat, true); + }); + }); + + describe('Pinning Notes', () => { + let alice: LoginUser, bob: LoginUser; + let aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + aliceInB = await resolveRemoteUser('a.test', alice.id, bob); + + await bob.client.request('following/create', { userId: aliceInB.id }); + }); + + test('Pinning localOnly Note is not delivered', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', localOnly: true })).createdNote; + await alice.client.request('i/pin', { noteId: note.id }); + await sleep(); + + const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(_aliceInB.pinnedNoteIds.length, 0); + }); + + test('Pinning followers-only Note is not delivered', async () => { + const note = (await alice.client.request('notes/create', { text: 'a', visibility: 'followers' })).createdNote; + await alice.client.request('i/pin', { noteId: note.id }); + await sleep(); + + const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(_aliceInB.pinnedNoteIds.length, 0); + }); + + let pinnedNote: Misskey.entities.Note; + + test('Pinning normal Note is delivered', async () => { + pinnedNote = (await alice.client.request('notes/create', { text: 'a' })).createdNote; + await alice.client.request('i/pin', { noteId: pinnedNote.id }); + await sleep(); + + const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(_aliceInB.pinnedNoteIds.length, 1); + const pinnedNoteInB = await resolveRemoteNote('a.test', pinnedNote.id, bob); + strictEqual(_aliceInB.pinnedNotes[0].id, pinnedNoteInB.id); + }); + + test('Unpinning normal Note is delivered', async () => { + await alice.client.request('i/unpin', { noteId: pinnedNote.id }); + await sleep(); + + const _aliceInB = await bob.client.request('users/show', { userId: aliceInB.id }); + strictEqual(_aliceInB.pinnedNoteIds.length, 0); + }); + }); + }); + + describe('Follow / Unfollow', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + describe('Follow a.test ==> b.test', () => { + beforeAll(async () => { + await alice.client.request('following/create', { userId: bobInA.id }); + + await sleep(); + }); + + test('Check consistency with `users/following` and `users/followers` endpoints', async () => { + await Promise.all([ + strictEqual( + (await alice.client.request('users/following', { userId: alice.id })) + .some(v => v.followeeId === bobInA.id), + true, + ), + strictEqual( + (await bob.client.request('users/followers', { userId: bob.id })) + .some(v => v.followerId === aliceInB.id), + true, + ), + ]); + }); + }); + + describe('Unfollow a.test ==> b.test', () => { + beforeAll(async () => { + await alice.client.request('following/delete', { userId: bobInA.id }); + + await sleep(); + }); + + test('Check consistency with `users/following` and `users/followers` endpoints', async () => { + await Promise.all([ + strictEqual( + (await alice.client.request('users/following', { userId: alice.id })) + .some(v => v.followeeId === bobInA.id), + false, + ), + strictEqual( + (await bob.client.request('users/followers', { userId: bob.id })) + .some(v => v.followerId === aliceInB.id), + false, + ), + ]); + }); + }); + }); + + describe('Follow requests', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + + await alice.client.request('i/update', { isLocked: true }); + }); + + describe('Send follow request from Bob to Alice and cancel', () => { + describe('Bob sends follow request to Alice', () => { + beforeAll(async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + }); + + test('Alice should have a request', async () => { + const requests = await alice.client.request('following/requests/list', {}); + strictEqual(requests.length, 1); + strictEqual(requests[0].followee.id, alice.id); + strictEqual(requests[0].follower.id, bobInA.id); + }); + }); + + describe('Alice cancels it', () => { + beforeAll(async () => { + await bob.client.request('following/requests/cancel', { userId: aliceInB.id }); + await sleep(); + }); + + test('Alice should have no requests', async () => { + const requests = await alice.client.request('following/requests/list', {}); + strictEqual(requests.length, 0); + }); + }); + }); + + describe('Send follow request from Bob to Alice and reject', () => { + beforeAll(async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + await alice.client.request('following/requests/reject', { userId: bobInA.id }); + await sleep(); + }); + + test('Bob should have no requests', async () => { + await rejects( + async () => await bob.client.request('following/requests/cancel', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'FOLLOW_REQUEST_NOT_FOUND'); + return true; + }, + ); + }); + + test('Bob doesn\'t follow Alice', async () => { + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); + }); + }); + + describe('Send follow request from Bob to Alice and accept', () => { + beforeAll(async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + await alice.client.request('following/requests/accept', { userId: bobInA.id }); + await sleep(); + }); + + test('Bob follows Alice', async () => { + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 1); + strictEqual(following[0].followeeId, aliceInB.id); + strictEqual(following[0].followerId, bob.id); + }); + }); + }); + + describe('Deletion', () => { + describe('Check Delete consistency', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Bob follows Alice, and Alice deleted themself', async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); // followed by Bob + + await alice.client.request('i/delete-account', { password: alice.password }); + await sleep(); + + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); // no following relation + + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_USER'); + return true; + }, + ); + }); + }); + + describe('Deletion of remote user for moderation', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Bob follows Alice, then Alice gets deleted in B server', async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); // followed by Bob + + await bAdmin.client.request('admin/delete-account', { userId: aliceInB.id }); + await sleep(); + + /** + * FIXME: remote account is not deleted! + * @see https://github.com/misskey-dev/misskey/issues/14728 + */ + const deletedAlice = await bob.client.request('users/show', { userId: aliceInB.id }); + assert(deletedAlice.id, aliceInB.id); + + // TODO: why still following relation? + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 1); + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'ALREADY_FOLLOWING'); + return true; + }, + ); + }); + + test('Alice tries to follow Bob, but it is not processed', async () => { + await alice.client.request('following/create', { userId: bobInA.id }); + await sleep(); + + const following = await alice.client.request('users/following', { userId: alice.id }); + strictEqual(following.length, 0); // Not following Bob because B server doesn't return Accept + + const followers = await bob.client.request('users/followers', { userId: bob.id }); + strictEqual(followers.length, 0); // Alice's Follow is not processed + }); + }); + }); + + describe('Suspension', () => { + describe('Check suspend/unsuspend consistency', () => { + let alice: LoginUser, bob: LoginUser; + let bobInA: Misskey.entities.UserDetailedNotMe, aliceInB: Misskey.entities.UserDetailedNotMe; + + beforeAll(async () => { + [alice, bob] = await Promise.all([ + createAccount('a.test'), + createAccount('b.test'), + ]); + + [bobInA, aliceInB] = await Promise.all([ + resolveRemoteUser('b.test', bob.id, alice), + resolveRemoteUser('a.test', alice.id, bob), + ]); + }); + + test('Bob follows Alice, and Alice gets suspended, there is no following relation, and Bob fails to follow again', async () => { + await bob.client.request('following/create', { userId: aliceInB.id }); + await sleep(); + + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); // followed by Bob + + await aAdmin.client.request('admin/suspend-user', { userId: alice.id }); + await sleep(); + + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); // no following relation + + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_USER'); + return true; + }, + ); + }); + + test('Alice gets unsuspended, Bob succeeds in following Alice', async () => { + await aAdmin.client.request('admin/unsuspend-user', { userId: alice.id }); + await sleep(); + + const followers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(followers.length, 1); // FIXME: followers are not deleted?? + + /** + * FIXME: still rejected! + * seems to can't process Undo Delete activity because it is not implemented + * related @see https://github.com/misskey-dev/misskey/issues/13273 + */ + await rejects( + async () => await bob.client.request('following/create', { userId: aliceInB.id }), + (err: any) => { + strictEqual(err.code, 'NO_SUCH_USER'); + return true; + }, + ); + + // FIXME: resolving also fails + await rejects( + async () => await resolveRemoteUser('a.test', alice.id, bob), + (err: any) => { + strictEqual(err.code, 'INTERNAL_ERROR'); + return true; + }, + ); + }); + + /** + * instead of simple unsuspension, let's tell existence by following from Alice + */ + test('Alice can follow Bob', async () => { + await alice.client.request('following/create', { userId: bobInA.id }); + await sleep(); + + const bobFollowers = await bob.client.request('users/followers', { userId: bob.id }); + strictEqual(bobFollowers.length, 1); // followed by Alice + assert(bobFollowers[0].follower != null); + const renewedaliceInB = bobFollowers[0].follower; + assert(aliceInB.username === renewedaliceInB.username); + assert(aliceInB.host === renewedaliceInB.host); + assert(aliceInB.id !== renewedaliceInB.id); // TODO: Same username and host, but their ids are different! Is it OK? + + const following = await bob.client.request('users/following', { userId: bob.id }); + strictEqual(following.length, 0); // following are deleted + + // Bob tries to follow Alice + await bob.client.request('following/create', { userId: renewedaliceInB.id }); + await sleep(); + + const aliceFollowers = await alice.client.request('users/followers', { userId: alice.id }); + strictEqual(aliceFollowers.length, 1); + + // FIXME: but resolving still fails ... + await rejects( + async () => await resolveRemoteUser('a.test', alice.id, bob), + (err: any) => { + strictEqual(err.code, 'INTERNAL_ERROR'); + return true; + }, + ); + }); + }); + }); +}); diff --git a/packages/backend/test-federation/test/utils.ts b/packages/backend/test-federation/test/utils.ts new file mode 100644 index 0000000000..7b2d023685 --- /dev/null +++ b/packages/backend/test-federation/test/utils.ts @@ -0,0 +1,307 @@ +import { deepStrictEqual, strictEqual } from 'assert'; +import { readFile } from 'fs/promises'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import * as Misskey from 'misskey-js'; +import { WebSocket } from 'ws'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +export const ADMIN_PARAMS = { username: 'admin', password: 'admin' }; +const ADMIN_CACHE = new Map<Host, SigninResponse>(); + +await Promise.all([ + fetchAdmin('a.test'), + fetchAdmin('b.test'), +]); + +type SigninResponse = Omit<Misskey.entities.SigninFlowResponse & { finished: true }, 'finished'>; + +export type LoginUser = SigninResponse & { + client: Misskey.api.APIClient; + username: string; + password: string; +} + +/** used for avoiding overload and some endpoints */ +export type Request = < + E extends keyof Misskey.Endpoints, + P extends Misskey.Endpoints[E]['req'], +>( + endpoint: E, + params: P, + credential?: string | null, +) => Promise<Misskey.api.SwitchCaseResponseType<E, P>>; + +type Host = 'a.test' | 'b.test'; + +export async function sleep(ms = 200): Promise<void> { + return new Promise(resolve => setTimeout(resolve, ms)); +} + +async function signin( + host: Host, + params: Misskey.entities.SigninFlowRequest, +): Promise<SigninResponse> { + // wait for a second to prevent hit rate limit + await sleep(1000); + + return await (new Misskey.api.APIClient({ origin: `https://${host}` }).request as Request)('signin-flow', params) + .then(res => { + strictEqual(res.finished, true); + if (params.username === ADMIN_PARAMS.username) ADMIN_CACHE.set(host, res); + return res; + }) + .then(({ id, i }) => ({ id, i })) + .catch(async err => { + if (err.code === 'TOO_MANY_AUTHENTICATION_FAILURES') { + await sleep(Math.random() * 2000); + return await signin(host, params); + } + throw err; + }); +} + +async function createAdmin(host: Host): Promise<Misskey.entities.SignupResponse | undefined> { + const client = new Misskey.api.APIClient({ origin: `https://${host}` }); + return await client.request('admin/accounts/create', ADMIN_PARAMS).then(res => { + ADMIN_CACHE.set(host, { + id: res.id, + // @ts-expect-error FIXME: openapi-typescript generates incorrect response type for this endpoint, so ignore this + i: res.token, + }); + return res as Misskey.entities.SignupResponse; + }).then(async res => { + await client.request('admin/roles/update-default-policies', { + policies: { + /** TODO: @see https://github.com/misskey-dev/misskey/issues/14169 */ + rateLimitFactor: 0 as never, + }, + }, res.token); + return res; + }).catch(err => { + if (err.info.e.message === 'access denied') return undefined; + throw err; + }); +} + +export async function fetchAdmin(host: Host): Promise<LoginUser> { + const admin = ADMIN_CACHE.get(host) ?? await signin(host, ADMIN_PARAMS) + .catch(async err => { + if (err.id === '6cc579cc-885d-43d8-95c2-b8c7fc963280') { + await createAdmin(host); + return await signin(host, ADMIN_PARAMS); + } + throw err; + }); + + return { + ...admin, + client: new Misskey.api.APIClient({ origin: `https://${host}`, credential: admin.i }), + ...ADMIN_PARAMS, + }; +} + +export async function createAccount(host: Host): Promise<LoginUser> { + const username = crypto.randomUUID().replaceAll('-', '').substring(0, 20); + const password = crypto.randomUUID().replaceAll('-', ''); + const admin = await fetchAdmin(host); + await admin.client.request('admin/accounts/create', { username, password }); + const signinRes = await signin(host, { username, password }); + + return { + ...signinRes, + client: new Misskey.api.APIClient({ origin: `https://${host}`, credential: signinRes.i }), + username, + password, + }; +} + +export async function createModerator(host: Host): Promise<LoginUser> { + const user = await createAccount(host); + const role = await createRole(host, { + name: 'Moderator', + isModerator: true, + }); + const admin = await fetchAdmin(host); + await admin.client.request('admin/roles/assign', { roleId: role.id, userId: user.id }); + return user; +} + +export async function createRole( + host: Host, + params: Partial<Misskey.entities.AdminRolesCreateRequest> = {}, +): Promise<Misskey.entities.Role> { + const admin = await fetchAdmin(host); + return await admin.client.request('admin/roles/create', { + name: 'Some role', + description: 'Role for testing', + color: null, + iconUrl: null, + target: 'conditional', + condFormula: {}, + isPublic: true, + isModerator: false, + isAdministrator: false, + isExplorable: true, + asBadge: false, + canEditMembersByModerator: false, + displayOrder: 0, + policies: {}, + ...params, + }); +} + +export async function resolveRemoteUser( + host: Host, + id: string, + from: LoginUser, +): Promise<Misskey.entities.UserDetailedNotMe> { + const uri = `https://${host}/users/${id}`; + return await from.client.request('ap/show', { uri }) + .then(res => { + strictEqual(res.type, 'User'); + strictEqual(res.object.uri, uri); + return res.object; + }); +} + +export async function resolveRemoteNote( + host: Host, + id: string, + from: LoginUser, +): Promise<Misskey.entities.Note> { + const uri = `https://${host}/notes/${id}`; + return await from.client.request('ap/show', { uri }) + .then(res => { + strictEqual(res.type, 'Note'); + strictEqual(res.object.uri, uri); + return res.object; + }); +} + +export async function uploadFile( + host: Host, + user: { i: string }, + path = '../../test/resources/192.jpg', +): Promise<Misskey.entities.DriveFile> { + const filename = path.split('/').pop() ?? 'untitled'; + const blob = new Blob([await readFile(join(__dirname, path))]); + + const body = new FormData(); + body.append('i', user.i); + body.append('force', 'true'); + body.append('file', blob); + body.append('name', filename); + + return await fetch(`https://${host}/api/drive/files/create`, { method: 'POST', body }) + .then(async res => await res.json()); +} + +export async function addCustomEmoji( + host: Host, + param?: Partial<Misskey.entities.AdminEmojiAddRequest>, + path?: string, +): Promise<Misskey.entities.EmojiDetailed> { + const admin = await fetchAdmin(host); + const name = crypto.randomUUID().replaceAll('-', ''); + const file = await uploadFile(host, admin, path); + return await admin.client.request('admin/emoji/add', { name, fileId: file.id, ...param }); +} + +export function deepStrictEqualWithExcludedFields<T>(actual: T, expected: T, excludedFields: (keyof T)[]) { + const _actual = structuredClone(actual); + const _expected = structuredClone(expected); + for (const obj of [_actual, _expected]) { + for (const field of excludedFields) { + delete obj[field]; + } + } + deepStrictEqual(_actual, _expected); +} + +export async function isFired<C extends keyof Misskey.Channels, T extends keyof Misskey.Channels[C]['events']>( + host: Host, + user: { i: string }, + channel: C, + trigger: () => Promise<unknown>, + type: T, + // @ts-expect-error TODO: why getting error here? + cond: (msg: Parameters<Misskey.Channels[C]['events'][T]>[0]) => boolean, + params?: Misskey.Channels[C]['params'], +): Promise<boolean> { + return new Promise<boolean>(async (resolve, reject) => { + const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + const connection = stream.useChannel(channel, params); + connection.on(type as any, ((msg: any) => { + if (cond(msg)) { + stream.close(); + clearTimeout(timer); + resolve(true); + } + }) as any); + + let timer: NodeJS.Timeout | undefined; + + await trigger().then(() => { + timer = setTimeout(() => { + stream.close(); + resolve(false); + }, 500); + }).catch(err => { + stream.close(); + clearTimeout(timer); + reject(err); + }); + }); +} + +export async function isNoteUpdatedEventFired( + host: Host, + user: { i: string }, + noteId: string, + trigger: () => Promise<unknown>, + cond: (msg: Parameters<Misskey.StreamEvents['noteUpdated']>[0]) => boolean, +): Promise<boolean> { + return new Promise<boolean>(async (resolve, reject) => { + const stream = new Misskey.Stream(`wss://${host}`, { token: user.i }, { WebSocket }); + stream.send('s', { id: noteId }); + stream.on('noteUpdated', msg => { + if (cond(msg)) { + stream.close(); + clearTimeout(timer); + resolve(true); + } + }); + + let timer: NodeJS.Timeout | undefined; + + await trigger().then(() => { + timer = setTimeout(() => { + stream.close(); + resolve(false); + }, 500); + }).catch(err => { + stream.close(); + clearTimeout(timer); + reject(err); + }); + }); +} + +export async function assertNotificationReceived( + receiverHost: Host, + receiver: LoginUser, + trigger: () => Promise<unknown>, + cond: (notification: Misskey.entities.Notification) => boolean, + expect: boolean, +) { + const streamingFired = await isFired(receiverHost, receiver, 'main', trigger, 'notification', cond); + strictEqual(streamingFired, expect); + + const endpointFired = await receiver.client.request('i/notifications', {}) + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + .then(([notification]) => notification != null ? cond(notification) : false); + strictEqual(endpointFired, expect); +} diff --git a/packages/backend/test-federation/tsconfig.json b/packages/backend/test-federation/tsconfig.json new file mode 100644 index 0000000000..3a1cb3b9f3 --- /dev/null +++ b/packages/backend/test-federation/tsconfig.json @@ -0,0 +1,114 @@ +{ + "compilerOptions": { + /* Visit https://aka.ms/tsconfig to read more about this file */ + + /* Projects */ + // "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */ + // "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */ + // "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */ + // "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */ + // "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */ + // "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */ + + /* Language and Environment */ + "target": "ESNext", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */ + // "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */ + // "jsx": "preserve", /* Specify what JSX code is generated. */ + // "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */ + // "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */ + // "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */ + // "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */ + // "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */ + // "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */ + // "noLib": true, /* Disable including any library files, including the default lib.d.ts. */ + // "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */ + // "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */ + + /* Modules */ + "module": "NodeNext", /* Specify what module code is generated. */ + // "rootDir": "./", /* Specify the root folder within your source files. */ + // "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */ + // "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */ + // "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */ + // "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */ + // "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */ + // "types": [], /* Specify type package names to be included without being referenced in a source file. */ + // "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */ + // "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */ + // "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */ + // "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */ + // "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */ + // "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */ + // "noUncheckedSideEffectImports": true, /* Check side effect imports. */ + // "resolveJsonModule": true, /* Enable importing .json files. */ + // "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */ + // "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */ + + /* JavaScript Support */ + // "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */ + // "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */ + // "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */ + + /* Emit */ + // "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */ + // "declarationMap": true, /* Create sourcemaps for d.ts files. */ + // "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */ + // "sourceMap": true, /* Create source map files for emitted JavaScript files. */ + // "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */ + // "noEmit": true, /* Disable emitting files from a compilation. */ + // "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */ + "outDir": "./built", /* Specify an output folder for all emitted files. */ + // "removeComments": true, /* Disable emitting comments. */ + // "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */ + // "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */ + // "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */ + // "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */ + // "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */ + // "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */ + // "newLine": "crlf", /* Set the newline character for emitting files. */ + // "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */ + // "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */ + // "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */ + // "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */ + // "declarationDir": "./", /* Specify the output directory for generated declaration files. */ + + /* Interop Constraints */ + // "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */ + // "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */ + // "isolatedDeclarations": true, /* Require sufficient annotation on exports so other tools can trivially generate declaration files. */ + // "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */ + "esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */ + // "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */ + "forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */ + + /* Type Checking */ + "strict": true, /* Enable all strict type-checking options. */ + // "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */ + // "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */ + // "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */ + // "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */ + // "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */ + // "strictBuiltinIteratorReturn": true, /* Built-in iterators are instantiated with a 'TReturn' type of 'undefined' instead of 'any'. */ + // "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */ + // "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */ + // "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */ + // "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */ + // "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */ + // "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */ + // "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */ + // "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */ + // "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */ + // "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */ + // "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */ + // "allowUnusedLabels": true, /* Disable error reporting for unused labels. */ + // "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */ + + /* Completeness */ + // "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */ + "skipLibCheck": true /* Skip type checking all .d.ts files. */ + }, + "include": [ + "daemon.ts", + "./test/**/*.ts" + ] +} diff --git a/packages/backend/test/e2e/2fa.ts b/packages/backend/test/e2e/2fa.ts index 48da6ba27f..289359a2ce 100644 --- a/packages/backend/test/e2e/2fa.ts +++ b/packages/backend/test/e2e/2fa.ts @@ -138,13 +138,7 @@ describe('2è¦ç´ èªè¨¼', () => { keyName: string, credentialId: Buffer, requestOptions: PublicKeyCredentialRequestOptionsJSON, - }): { - username: string, - password: string, - credential: AuthenticationResponseJSON, - 'g-recaptcha-response'?: string | null, - 'hcaptcha-response'?: string | null, - } => { + }): misskey.entities.SigninFlowRequest => { // AuthenticatorAssertionResponse.authenticatorData // https://developer.mozilla.org/en-US/docs/Web/API/AuthenticatorAssertionResponse/authenticatorData const authenticatorData = Buffer.concat([ @@ -204,17 +198,21 @@ describe('2è¦ç´ èªè¨¼', () => { }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('users/show', { - username, - }, alice); - assert.strictEqual(usersShowResponse.status, 200); - assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true); + const signinWithoutTokenResponse = await api('signin-flow', { + ...signinParam(), + }); + assert.strictEqual(signinWithoutTokenResponse.status, 200); + assert.deepStrictEqual(signinWithoutTokenResponse.body, { + finished: false, + next: 'totp', + }); - const signinResponse = await api('signin', { + const signinResponse = await api('signin-flow', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); assert.strictEqual(signinResponse.status, 200); + assert.strictEqual(signinResponse.body.finished, true); assert.notEqual(signinResponse.body.i, undefined); // 後片付㑠@@ -255,27 +253,23 @@ describe('2è¦ç´ èªè¨¼', () => { assert.strictEqual(keyDoneResponse.body.id, credentialId.toString('base64url')); assert.strictEqual(keyDoneResponse.body.name, keyName); - const usersShowResponse = await api('users/show', { - username, - }); - assert.strictEqual(usersShowResponse.status, 200); - assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, true); - - const signinResponse = await api('signin', { + const signinResponse = await api('signin-flow', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); - assert.strictEqual(signinResponse.body.i, undefined); - assert.notEqual((signinResponse.body as unknown as { challenge: unknown | undefined }).challenge, undefined); - assert.notEqual((signinResponse.body as unknown as { allowCredentials: unknown | undefined }).allowCredentials, undefined); - assert.strictEqual((signinResponse.body as unknown as { allowCredentials: {id: string}[] }).allowCredentials[0].id, credentialId.toString('base64url')); + assert.strictEqual(signinResponse.body.finished, false); + assert.strictEqual(signinResponse.body.next, 'passkey'); + assert.notEqual(signinResponse.body.authRequest.challenge, undefined); + assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined); + assert.strictEqual(signinResponse.body.authRequest.allowCredentials && signinResponse.body.authRequest.allowCredentials[0]?.id, credentialId.toString('base64url')); - const signinResponse2 = await api('signin', signinWithSecurityKeyParam({ + const signinResponse2 = await api('signin-flow', signinWithSecurityKeyParam({ keyName, credentialId, - requestOptions: signinResponse.body, - } as any)); + requestOptions: signinResponse.body.authRequest, + })); assert.strictEqual(signinResponse2.status, 200); + assert.strictEqual(signinResponse2.body.finished, true); assert.notEqual(signinResponse2.body.i, undefined); // 後片付㑠@@ -317,28 +311,30 @@ describe('2è¦ç´ èªè¨¼', () => { }, alice); assert.strictEqual(passwordLessResponse.status, 204); - const usersShowResponse = await api('users/show', { - username, - }); - assert.strictEqual(usersShowResponse.status, 200); - assert.strictEqual((usersShowResponse.body as unknown as { usePasswordLessLogin: boolean }).usePasswordLessLogin, true); + const iResponse = await api('i', {}, alice); + assert.strictEqual(iResponse.status, 200); + assert.strictEqual(iResponse.body.usePasswordLessLogin, true); - const signinResponse = await api('signin', { + const signinResponse = await api('signin-flow', { ...signinParam(), password: '', }); assert.strictEqual(signinResponse.status, 200); - assert.strictEqual(signinResponse.body.i, undefined); + assert.strictEqual(signinResponse.body.finished, false); + assert.strictEqual(signinResponse.body.next, 'passkey'); + assert.notEqual(signinResponse.body.authRequest.challenge, undefined); + assert.notEqual(signinResponse.body.authRequest.allowCredentials, undefined); - const signinResponse2 = await api('signin', { + const signinResponse2 = await api('signin-flow', { ...signinWithSecurityKeyParam({ keyName, credentialId, - requestOptions: signinResponse.body, + requestOptions: signinResponse.body.authRequest, } as any), password: '', }); assert.strictEqual(signinResponse2.status, 200); + assert.strictEqual(signinResponse2.body.finished, true); assert.notEqual(signinResponse2.body.i, undefined); // 後片付㑠@@ -426,11 +422,11 @@ describe('2è¦ç´ èªè¨¼', () => { assert.strictEqual(keyDoneResponse.status, 200); // テストã®å®Ÿè¡Œé †ã«ã‚ˆã£ã¦ã¯è¤‡æ•°æ®‹ã£ã¦ã‚‹ã®ã§å…¨éƒ¨æ¶ˆã™ - const iResponse = await api('i', { + const beforeIResponse = await api('i', { }, alice); - assert.strictEqual(iResponse.status, 200); - assert.ok(iResponse.body.securityKeysList); - for (const key of iResponse.body.securityKeysList) { + assert.strictEqual(beforeIResponse.status, 200); + assert.ok(beforeIResponse.body.securityKeysList); + for (const key of beforeIResponse.body.securityKeysList) { const removeKeyResponse = await api('i/2fa/remove-key', { token: otpToken(registerResponse.body.secret), password, @@ -439,17 +435,16 @@ describe('2è¦ç´ èªè¨¼', () => { assert.strictEqual(removeKeyResponse.status, 200); } - const usersShowResponse = await api('users/show', { - username, - }); - assert.strictEqual(usersShowResponse.status, 200); - assert.strictEqual((usersShowResponse.body as unknown as { securityKeys: boolean }).securityKeys, false); + const afterIResponse = await api('i', {}, alice); + assert.strictEqual(afterIResponse.status, 200); + assert.strictEqual(afterIResponse.body.securityKeys, false); - const signinResponse = await api('signin', { + const signinResponse = await api('signin-flow', { ...signinParam(), token: otpToken(registerResponse.body.secret), }); assert.strictEqual(signinResponse.status, 200); + assert.strictEqual(signinResponse.body.finished, true); assert.notEqual(signinResponse.body.i, undefined); // 後片付㑠@@ -470,11 +465,9 @@ describe('2è¦ç´ èªè¨¼', () => { }, alice); assert.strictEqual(doneResponse.status, 200); - const usersShowResponse = await api('users/show', { - username, - }); - assert.strictEqual(usersShowResponse.status, 200); - assert.strictEqual((usersShowResponse.body as unknown as { twoFactorEnabled: boolean }).twoFactorEnabled, true); + const iResponse = await api('i', {}, alice); + assert.strictEqual(iResponse.status, 200); + assert.strictEqual(iResponse.body.twoFactorEnabled, true); const unregisterResponse = await api('i/2fa/unregister', { token: otpToken(registerResponse.body.secret), @@ -482,10 +475,11 @@ describe('2è¦ç´ èªè¨¼', () => { }, alice); assert.strictEqual(unregisterResponse.status, 204); - const signinResponse = await api('signin', { + const signinResponse = await api('signin-flow', { ...signinParam(), }); assert.strictEqual(signinResponse.status, 200); + assert.strictEqual(signinResponse.body.finished, true); assert.notEqual(signinResponse.body.i, undefined); // 後片付㑠diff --git a/packages/backend/test/e2e/endpoints.ts b/packages/backend/test/e2e/endpoints.ts index 5aaec7f6f9..b91d77c398 100644 --- a/packages/backend/test/e2e/endpoints.ts +++ b/packages/backend/test/e2e/endpoints.ts @@ -66,9 +66,9 @@ describe('Endpoints', () => { }); }); - describe('signin', () => { + describe('signin-flow', () => { test('é–“é•ã£ãŸãƒ‘スワードã§ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã§ããªã„', async () => { - const res = await api('signin', { + const res = await api('signin-flow', { username: 'test1', password: 'bar', }); @@ -77,7 +77,7 @@ describe('Endpoints', () => { }); test('クエリをインジェクションã§ããªã„', async () => { - const res = await api('signin', { + const res = await api('signin-flow', { username: 'test1', // @ts-expect-error password must be string password: { @@ -89,7 +89,7 @@ describe('Endpoints', () => { }); test('æ£ã—ã„æƒ…å ±ã§ã‚µã‚¤ãƒ³ã‚¤ãƒ³ã§ãã‚‹', async () => { - const res = await api('signin', { + const res = await api('signin-flow', { username: 'test1', password: 'test1', }); diff --git a/packages/backend/test/e2e/fetch-resource.ts b/packages/backend/test/e2e/fetch-resource.ts index c8f5debbc9..0322ac5546 100644 --- a/packages/backend/test/e2e/fetch-resource.ts +++ b/packages/backend/test/e2e/fetch-resource.ts @@ -230,6 +230,7 @@ describe('Webリソース', () => { path: path('xxxxxxxxxx'), type: HTML, })); + test.todo('HTMLã¨ã—ã¦GETã§ãる。(リモートユーザーã§ã‚‚リダイレクトã›ãš)'); }); describe.each([ @@ -249,6 +250,7 @@ describe('Webリソース', () => { path: path('xxxxxxxxxx'), accept, })); + test.todo('ã¯ã‚ªãƒªã‚¸ãƒŠãƒ«ã«ãƒªãƒ€ã‚¤ãƒ¬ã‚¯ãƒˆã•れる。(リモートユーザー)'); }); }); diff --git a/packages/backend/test/e2e/synalio/abuse-report.ts b/packages/backend/test/e2e/synalio/abuse-report.ts index 6ce6e47781..c98d199f35 100644 --- a/packages/backend/test/e2e/synalio/abuse-report.ts +++ b/packages/backend/test/e2e/synalio/abuse-report.ts @@ -157,7 +157,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: webhookBody1.body.id, - forward: false, }, admin); }); @@ -214,7 +213,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, - forward: false, }, admin); }); @@ -257,7 +255,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: webhookBody1.body.id, - forward: false, }, admin); }).catch(e => e.message); @@ -288,7 +285,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, - forward: false, }, admin); }).catch(e => e.message); @@ -319,7 +315,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, - forward: false, }, admin); }).catch(e => e.message); @@ -350,7 +345,6 @@ describe('[シナリオ] ãƒ¦ãƒ¼ã‚¶é€šå ±', () => { const webhookBody2 = await captureWebhook(async () => { await resolveAbuseReport({ reportId: abuseReportId, - forward: false, }, admin); }).catch(e => e.message); diff --git a/packages/backend/test/e2e/users.ts b/packages/backend/test/e2e/users.ts index 7d2e14f85d..7b21834b57 100644 --- a/packages/backend/test/e2e/users.ts +++ b/packages/backend/test/e2e/users.ts @@ -86,9 +86,6 @@ describe('ユーザー', () => { publicReactions: user.publicReactions, followingVisibility: user.followingVisibility, followersVisibility: user.followersVisibility, - twoFactorEnabled: user.twoFactorEnabled, - usePasswordLessLogin: user.usePasswordLessLogin, - securityKeys: user.securityKeys, roles: user.roles, memo: user.memo, }); @@ -153,6 +150,9 @@ describe('ユーザー', () => { achievements: user.achievements, loggedInDays: user.loggedInDays, policies: user.policies, + twoFactorEnabled: user.twoFactorEnabled, + usePasswordLessLogin: user.usePasswordLessLogin, + securityKeys: user.securityKeys, ...(security ? { email: user.email, emailVerified: user.emailVerified, @@ -350,9 +350,6 @@ describe('ユーザー', () => { assert.strictEqual(response.publicReactions, true); assert.strictEqual(response.followingVisibility, 'public'); assert.strictEqual(response.followersVisibility, 'public'); - assert.strictEqual(response.twoFactorEnabled, false); - assert.strictEqual(response.usePasswordLessLogin, false); - assert.strictEqual(response.securityKeys, false); assert.deepStrictEqual(response.roles, []); assert.strictEqual(response.memo, null); @@ -393,6 +390,9 @@ describe('ユーザー', () => { assert.deepStrictEqual(response.achievements, []); assert.deepStrictEqual(response.loggedInDays, 0); assert.deepStrictEqual(response.policies, DEFAULT_POLICIES); + assert.strictEqual(response.twoFactorEnabled, false); + assert.strictEqual(response.usePasswordLessLogin, false); + assert.strictEqual(response.securityKeys, false); assert.notStrictEqual(response.email, undefined); assert.strictEqual(response.emailVerified, false); assert.deepStrictEqual(response.securityKeysList, []); @@ -649,6 +649,9 @@ describe('ユーザー', () => { { label: '自分以外ã‹ã‚‰è¦‹ãŸã¨ãã¯Administratorã‹åˆ¤å®šã§ããªã„', user: () => userAdmin, selector: (user: misskey.entities.UserDetailedNotMe) => user.isAdmin, expected: () => undefined }, { label: 'Moderatorã«ãªã£ã¦ã„ã‚‹', user: () => userModerator, me: () => userModerator, selector: (user: misskey.entities.MeDetailed) => user.isModerator }, { label: '自分以外ã‹ã‚‰è¦‹ãŸã¨ãã¯Moderatorã‹åˆ¤å®šã§ããªã„', user: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.isModerator, expected: () => undefined }, + { label: '自分ã‹ã‚‰è¦‹ãŸå ´åˆã«äºŒè¦ç´ èªè¨¼é–¢é€£ã®ãƒ—ãƒãƒ‘ティãŒã‚»ãƒƒãƒˆã•れã¦ã„ã‚‹', user: () => alice, me: () => alice, selector: (user: misskey.entities.MeDetailed) => user.twoFactorEnabled, expected: () => false }, + { label: '自分以外ã‹ã‚‰è¦‹ãŸå ´åˆã«äºŒè¦ç´ èªè¨¼é–¢é€£ã®ãƒ—ãƒãƒ‘ティãŒã‚»ãƒƒãƒˆã•れã¦ã„ãªã„', user: () => alice, me: () => bob, selector: (user: misskey.entities.UserDetailedNotMe) => user.twoFactorEnabled, expected: () => undefined }, + { label: 'モデレーターã‹ã‚‰è¦‹ãŸå ´åˆã«äºŒè¦ç´ èªè¨¼é–¢é€£ã®ãƒ—ãƒãƒ‘ティãŒã‚»ãƒƒãƒˆã•れã¦ã„ã‚‹', user: () => alice, me: () => userModerator, selector: (user: misskey.entities.UserDetailedNotMe) => user.twoFactorEnabled, expected: () => false }, { label: 'サイレンスã«ãªã£ã¦ã„ã‚‹', user: () => userSilenced, selector: (user: misskey.entities.UserDetailed) => user.isSilenced }, // FIXME: è½ã¡ã‚‹ //{ label: 'サスペンドã«ãªã£ã¦ã„ã‚‹', user: () => userSuspended, selector: (user: misskey.entities.UserDetailed) => user.isSuspended }, diff --git a/packages/backend/test/unit/AbuseReportNotificationService.ts b/packages/backend/test/unit/AbuseReportNotificationService.ts index e971659070..235af29f0d 100644 --- a/packages/backend/test/unit/AbuseReportNotificationService.ts +++ b/packages/backend/test/unit/AbuseReportNotificationService.ts @@ -5,6 +5,7 @@ import { jest } from '@jest/globals'; import { Test, TestingModule } from '@nestjs/testing'; +import { randomString } from '../utils.js'; import { AbuseReportNotificationService } from '@/core/AbuseReportNotificationService.js'; import { AbuseReportNotificationRecipientRepository, @@ -25,7 +26,7 @@ import { ModerationLogService } from '@/core/ModerationLogService.js'; import { GlobalEventService } from '@/core/GlobalEventService.js'; import { RecipientMethod } from '@/models/AbuseReportNotificationRecipient.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; -import { randomString } from '../utils.js'; +import { UserEntityService } from '@/core/entities/UserEntityService.js'; process.env.NODE_ENV = 'test'; @@ -111,6 +112,9 @@ describe('AbuseReportNotificationService', () => { provide: SystemWebhookService, useFactory: () => ({ enqueueSystemWebhook: jest.fn() }), }, { + provide: UserEntityService, useFactory: () => ({ pack: (v: any) => v }), + }, + { provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }), }, { diff --git a/packages/backend/test/unit/FetchInstanceMetadataService.ts b/packages/backend/test/unit/FetchInstanceMetadataService.ts index bf8f3ab0e3..1e3605aafc 100644 --- a/packages/backend/test/unit/FetchInstanceMetadataService.ts +++ b/packages/backend/test/unit/FetchInstanceMetadataService.ts @@ -8,6 +8,7 @@ process.env.NODE_ENV = 'test'; import { jest } from '@jest/globals'; import { Test } from '@nestjs/testing'; import { Redis } from 'ioredis'; +import type { TestingModule } from '@nestjs/testing'; import { GlobalModule } from '@/GlobalModule.js'; import { FetchInstanceMetadataService } from '@/core/FetchInstanceMetadataService.js'; import { FederatedInstanceService } from '@/core/FederatedInstanceService.js'; @@ -16,7 +17,6 @@ import { LoggerService } from '@/core/LoggerService.js'; import { UtilityService } from '@/core/UtilityService.js'; import { IdService } from '@/core/IdService.js'; import { DI } from '@/di-symbols.js'; -import type { TestingModule } from '@nestjs/testing'; function mockRedis() { const hash = {} as any; @@ -52,7 +52,7 @@ describe('FetchInstanceMetadataService', () => { if (token === HttpRequestService) { return { getJson: jest.fn(), getHtml: jest.fn(), send: jest.fn() }; } else if (token === FederatedInstanceService) { - return { fetch: jest.fn() }; + return { fetchOrRegister: jest.fn() }; } else if (token === DI.redis) { return mockRedis; } @@ -75,7 +75,7 @@ describe('FetchInstanceMetadataService', () => { test('Lock and update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any); + federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => { return now - 10 * 1000 * 60 * 60 * 24; } } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); @@ -83,14 +83,14 @@ describe('FetchInstanceMetadataService', () => { await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); - expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); + expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1); expect(httpRequestService.getJson).toHaveBeenCalled(); }); test('Lock and don\'t update', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any); + federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); const unlockSpy = jest.spyOn(fetchInstanceMetadataService, 'unlock'); @@ -98,14 +98,14 @@ describe('FetchInstanceMetadataService', () => { await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(1); - expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(1); + expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(1); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); }); test('Do nothing when lock not acquired', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); + federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); await fetchInstanceMetadataService.tryLock('example.com'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); @@ -114,14 +114,14 @@ describe('FetchInstanceMetadataService', () => { await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any); expect(tryLockSpy).toHaveBeenCalledTimes(1); expect(unlockSpy).toHaveBeenCalledTimes(0); - expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); + expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalledTimes(0); }); test('Do when lock not acquired but forced', async () => { redisClient.set = mockRedis(); const now = Date.now(); - federatedInstanceService.fetch.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); + federatedInstanceService.fetchOrRegister.mockResolvedValue({ infoUpdatedAt: { getTime: () => now - 10 * 1000 * 60 * 60 * 24 } } as any); httpRequestService.getJson.mockImplementation(() => { throw Error(); }); await fetchInstanceMetadataService.tryLock('example.com'); const tryLockSpy = jest.spyOn(fetchInstanceMetadataService, 'tryLock'); @@ -130,7 +130,7 @@ describe('FetchInstanceMetadataService', () => { await fetchInstanceMetadataService.fetchInstanceMetadata({ host: 'example.com' } as any, true); expect(tryLockSpy).toHaveBeenCalledTimes(0); expect(unlockSpy).toHaveBeenCalledTimes(1); - expect(federatedInstanceService.fetch).toHaveBeenCalledTimes(0); + expect(federatedInstanceService.fetchOrRegister).toHaveBeenCalledTimes(0); expect(httpRequestService.getJson).toHaveBeenCalled(); }); }); diff --git a/packages/backend/test/unit/FlashService.ts b/packages/backend/test/unit/FlashService.ts new file mode 100644 index 0000000000..12ffaf3421 --- /dev/null +++ b/packages/backend/test/unit/FlashService.ts @@ -0,0 +1,152 @@ +/* eslint-disable @typescript-eslint/no-unused-vars */ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { Test, TestingModule } from '@nestjs/testing'; +import { FlashService } from '@/core/FlashService.js'; +import { IdService } from '@/core/IdService.js'; +import { FlashsRepository, MiFlash, MiUser, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { DI } from '@/di-symbols.js'; +import { GlobalModule } from '@/GlobalModule.js'; + +describe('FlashService', () => { + let app: TestingModule; + let service: FlashService; + + // -------------------------------------------------------------------------------------- + + let flashsRepository: FlashsRepository; + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let idService: IdService; + + // -------------------------------------------------------------------------------------- + + let root: MiUser; + let alice: MiUser; + let bob: MiUser; + + // -------------------------------------------------------------------------------------- + + async function createFlash(data: Partial<MiFlash>) { + return flashsRepository.insert({ + id: idService.gen(), + updatedAt: new Date(), + userId: root.id, + title: 'title', + summary: 'summary', + script: 'script', + permissions: [], + likedCount: 0, + ...data, + }).then(x => flashsRepository.findOneByOrFail(x.identifiers[0])); + } + + async function createUser(data: Partial<MiUser> = {}) { + const user = await usersRepository + .insert({ + id: idService.gen(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + }); + + return user; + } + + // -------------------------------------------------------------------------------------- + + beforeEach(async () => { + app = await Test.createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + FlashService, + IdService, + ], + }).compile(); + + service = app.get(FlashService); + + flashsRepository = app.get(DI.flashsRepository); + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + idService = app.get(IdService); + + root = await createUser({ username: 'root', usernameLower: 'root', isRoot: true }); + alice = await createUser({ username: 'alice', usernameLower: 'alice', isRoot: false }); + bob = await createUser({ username: 'bob', usernameLower: 'bob', isRoot: false }); + }); + + afterEach(async () => { + await usersRepository.delete({}); + await userProfilesRepository.delete({}); + await flashsRepository.delete({}); + }); + + afterAll(async () => { + await app.close(); + }); + + // -------------------------------------------------------------------------------------- + + describe('featured', () => { + test('should return featured flashes', async () => { + const flash1 = await createFlash({ likedCount: 1 }); + const flash2 = await createFlash({ likedCount: 2 }); + const flash3 = await createFlash({ likedCount: 3 }); + + const result = await service.featured({ + offset: 0, + limit: 10, + }); + + expect(result).toEqual([flash3, flash2, flash1]); + }); + + test('should return featured flashes public visibility only', async () => { + const flash1 = await createFlash({ likedCount: 1, visibility: 'public' }); + const flash2 = await createFlash({ likedCount: 2, visibility: 'public' }); + const flash3 = await createFlash({ likedCount: 3, visibility: 'private' }); + + const result = await service.featured({ + offset: 0, + limit: 10, + }); + + expect(result).toEqual([flash2, flash1]); + }); + + test('should return featured flashes with offset', async () => { + const flash1 = await createFlash({ likedCount: 1 }); + const flash2 = await createFlash({ likedCount: 2 }); + const flash3 = await createFlash({ likedCount: 3 }); + + const result = await service.featured({ + offset: 1, + limit: 10, + }); + + expect(result).toEqual([flash2, flash1]); + }); + + test('should return featured flashes with limit', async () => { + const flash1 = await createFlash({ likedCount: 1 }); + const flash2 = await createFlash({ likedCount: 2 }); + const flash3 = await createFlash({ likedCount: 3 }); + + const result = await service.featured({ + offset: 0, + limit: 2, + }); + + expect(result).toEqual([flash3, flash2]); + }); + }); +}); diff --git a/packages/backend/test/unit/RoleService.ts b/packages/backend/test/unit/RoleService.ts index ef80d25f81..9c1b1008d6 100644 --- a/packages/backend/test/unit/RoleService.ts +++ b/packages/backend/test/unit/RoleService.ts @@ -10,6 +10,8 @@ import { jest } from '@jest/globals'; import { ModuleMocker } from 'jest-mock'; import { Test } from '@nestjs/testing'; import * as lolex from '@sinonjs/fake-timers'; +import type { TestingModule } from '@nestjs/testing'; +import type { MockFunctionMetadata } from 'jest-mock'; import { GlobalModule } from '@/GlobalModule.js'; import { RoleService } from '@/core/RoleService.js'; import { @@ -31,8 +33,6 @@ import { secureRndstr } from '@/misc/secure-rndstr.js'; import { NotificationService } from '@/core/NotificationService.js'; import { RoleCondFormulaValue } from '@/models/Role.js'; import { UserEntityService } from '@/core/entities/UserEntityService.js'; -import type { TestingModule } from '@nestjs/testing'; -import type { MockFunctionMetadata } from 'jest-mock'; const moduleMocker = new ModuleMocker(global); @@ -277,9 +277,9 @@ describe('RoleService', () => { }); describe('getModeratorIds', () => { - test('includeAdmins = false, excludeExpire = false', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = false, includeRoot = false, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -295,13 +295,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(false, false); + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: false, + excludeExpire: false, + }); expect(result).toEqual([modeUser1.id, modeUser2.id]); }); - test('includeAdmins = false, excludeExpire = true', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = false, includeRoot = false, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -317,13 +321,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(false, true); + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: false, + excludeExpire: true, + }); expect(result).toEqual([modeUser1.id]); }); - test('includeAdmins = true, excludeExpire = false', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = true, includeRoot = false, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -339,13 +347,17 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(true, false); + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: false, + excludeExpire: false, + }); expect(result).toEqual([adminUser1.id, adminUser2.id, modeUser1.id, modeUser2.id]); }); - test('includeAdmins = true, excludeExpire = true', async () => { - const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2] = await Promise.all([ - createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), + test('includeAdmins = true, includeRoot = false, excludeExpire = true', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), ]); const role1 = await createRole({ name: 'admin', isAdministrator: true }); @@ -361,9 +373,111 @@ describe('RoleService', () => { assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), ]); - const result = await roleService.getModeratorIds(true, true); + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: false, + excludeExpire: true, + }); expect(result).toEqual([adminUser1.id, modeUser1.id]); }); + + test('includeAdmins = false, includeRoot = true, excludeExpire = false', async () => { + const [adminUser1, adminUser2, modeUser1, modeUser2, normalUser1, normalUser2, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: adminUser2.id, roleId: role1.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: modeUser2.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + assignRole({ userId: normalUser2.id, roleId: role3.id, expiresAt: new Date(Date.now() - 1000) }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([modeUser1.id, modeUser2.id, rootUser.id]); + }); + + test('root has moderator role', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: rootUser.id, roleId: role2.id }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([modeUser1.id, rootUser.id]); + }); + + test('root has administrator role', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: rootUser.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: true, + includeRoot: true, + excludeExpire: false, + }); + expect(result).toEqual([adminUser1.id, modeUser1.id, rootUser.id]); + }); + + test('root has moderator role(expire)', async () => { + const [adminUser1, modeUser1, normalUser1, rootUser] = await Promise.all([ + createUser(), createUser(), createUser(), createUser({ isRoot: true }), + ]); + + const role1 = await createRole({ name: 'admin', isAdministrator: true }); + const role2 = await createRole({ name: 'moderator', isModerator: true }); + const role3 = await createRole({ name: 'normal' }); + + await Promise.all([ + assignRole({ userId: adminUser1.id, roleId: role1.id }), + assignRole({ userId: modeUser1.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: rootUser.id, roleId: role2.id, expiresAt: new Date(Date.now() - 1000) }), + assignRole({ userId: normalUser1.id, roleId: role3.id }), + ]); + + const result = await roleService.getModeratorIds({ + includeAdmins: false, + includeRoot: true, + excludeExpire: true, + }); + expect(result).toEqual([rootUser.id]); + }); }); describe('conditional role', () => { diff --git a/packages/backend/test/unit/WebhookTestService.ts b/packages/backend/test/unit/WebhookTestService.ts index 5e63b86f8f..be84ae9b84 100644 --- a/packages/backend/test/unit/WebhookTestService.ts +++ b/packages/backend/test/unit/WebhookTestService.ts @@ -7,7 +7,7 @@ import { Test, TestingModule } from '@nestjs/testing'; import { beforeAll, describe, jest } from '@jest/globals'; import { WebhookTestService } from '@/core/WebhookTestService.js'; -import { UserWebhookService } from '@/core/UserWebhookService.js'; +import { UserWebhookPayload, UserWebhookService } from '@/core/UserWebhookService.js'; import { SystemWebhookService } from '@/core/SystemWebhookService.js'; import { GlobalModule } from '@/GlobalModule.js'; import { MiSystemWebhook, MiUser, MiWebhook, UserProfilesRepository, UsersRepository } from '@/models/_.js'; @@ -122,7 +122,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('note'); - expect((calls[2] as any).id).toBe('dummy-note-1'); + expect((calls[2] as UserWebhookPayload<'note'>).note.id).toBe('dummy-note-1'); }); test('reply', async () => { @@ -131,7 +131,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('reply'); - expect((calls[2] as any).id).toBe('dummy-reply-1'); + expect((calls[2] as UserWebhookPayload<'reply'>).note.id).toBe('dummy-reply-1'); }); test('renote', async () => { @@ -140,7 +140,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('renote'); - expect((calls[2] as any).id).toBe('dummy-renote-1'); + expect((calls[2] as UserWebhookPayload<'renote'>).note.id).toBe('dummy-renote-1'); }); test('mention', async () => { @@ -149,7 +149,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('mention'); - expect((calls[2] as any).id).toBe('dummy-mention-1'); + expect((calls[2] as UserWebhookPayload<'mention'>).note.id).toBe('dummy-mention-1'); }); test('follow', async () => { @@ -158,7 +158,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('follow'); - expect((calls[2] as any).id).toBe('dummy-user-1'); + expect((calls[2] as UserWebhookPayload<'follow'>).user.id).toBe('dummy-user-1'); }); test('followed', async () => { @@ -167,7 +167,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('followed'); - expect((calls[2] as any).id).toBe('dummy-user-2'); + expect((calls[2] as UserWebhookPayload<'followed'>).user.id).toBe('dummy-user-2'); }); test('unfollow', async () => { @@ -176,7 +176,7 @@ describe('WebhookTestService', () => { const calls = queueService.userWebhookDeliver.mock.calls[0]; expect((calls[0] as any).id).toBe('dummy-webhook'); expect(calls[1]).toBe('unfollow'); - expect((calls[2] as any).id).toBe('dummy-user-3'); + expect((calls[2] as UserWebhookPayload<'unfollow'>).user.id).toBe('dummy-user-3'); }); describe('NoSuchWebhookError', () => { diff --git a/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts new file mode 100644 index 0000000000..1506283a3c --- /dev/null +++ b/packages/backend/test/unit/queue/processors/CheckModeratorsActivityProcessorService.ts @@ -0,0 +1,379 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { jest } from '@jest/globals'; +import { Test, TestingModule } from '@nestjs/testing'; +import * as lolex from '@sinonjs/fake-timers'; +import { addHours, addSeconds, subDays, subHours, subSeconds } from 'date-fns'; +import { CheckModeratorsActivityProcessorService } from '@/queue/processors/CheckModeratorsActivityProcessorService.js'; +import { MiSystemWebhook, MiUser, MiUserProfile, UserProfilesRepository, UsersRepository } from '@/models/_.js'; +import { IdService } from '@/core/IdService.js'; +import { RoleService } from '@/core/RoleService.js'; +import { GlobalModule } from '@/GlobalModule.js'; +import { MetaService } from '@/core/MetaService.js'; +import { DI } from '@/di-symbols.js'; +import { QueueLoggerService } from '@/queue/QueueLoggerService.js'; +import { EmailService } from '@/core/EmailService.js'; +import { SystemWebhookService } from '@/core/SystemWebhookService.js'; +import { AnnouncementService } from '@/core/AnnouncementService.js'; + +const baseDate = new Date(Date.UTC(2000, 11, 15, 12, 0, 0)); + +describe('CheckModeratorsActivityProcessorService', () => { + let app: TestingModule; + let clock: lolex.InstalledClock; + let service: CheckModeratorsActivityProcessorService; + + // -------------------------------------------------------------------------------------- + + let usersRepository: UsersRepository; + let userProfilesRepository: UserProfilesRepository; + let idService: IdService; + let roleService: jest.Mocked<RoleService>; + let announcementService: jest.Mocked<AnnouncementService>; + let emailService: jest.Mocked<EmailService>; + let systemWebhookService: jest.Mocked<SystemWebhookService>; + + let systemWebhook1: MiSystemWebhook; + let systemWebhook2: MiSystemWebhook; + let systemWebhook3: MiSystemWebhook; + + // -------------------------------------------------------------------------------------- + + async function createUser(data: Partial<MiUser> = {}, profile: Partial<MiUserProfile> = {}): Promise<MiUser> { + const id = idService.gen(); + const user = await usersRepository + .insert({ + id: id, + username: `user_${id}`, + usernameLower: `user_${id}`.toLowerCase(), + ...data, + }) + .then(x => usersRepository.findOneByOrFail(x.identifiers[0])); + + await userProfilesRepository.insert({ + userId: user.id, + ...profile, + }); + + return user; + } + + function crateSystemWebhook(data: Partial<MiSystemWebhook> = {}): MiSystemWebhook { + return { + id: idService.gen(), + isActive: true, + updatedAt: new Date(), + latestSentAt: null, + latestStatus: null, + name: 'test', + url: 'https://example.com', + secret: 'test', + on: [], + ...data, + }; + } + + function mockModeratorRole(users: MiUser[]) { + roleService.getModerators.mockReset(); + roleService.getModerators.mockResolvedValue(users); + } + + // -------------------------------------------------------------------------------------- + + beforeAll(async () => { + app = await Test + .createTestingModule({ + imports: [ + GlobalModule, + ], + providers: [ + CheckModeratorsActivityProcessorService, + IdService, + { + provide: RoleService, useFactory: () => ({ getModerators: jest.fn() }), + }, + { + provide: MetaService, useFactory: () => ({ fetch: jest.fn() }), + }, + { + provide: AnnouncementService, useFactory: () => ({ create: jest.fn() }), + }, + { + provide: EmailService, useFactory: () => ({ sendEmail: jest.fn() }), + }, + { + provide: SystemWebhookService, useFactory: () => ({ + fetchActiveSystemWebhooks: jest.fn(), + enqueueSystemWebhook: jest.fn(), + }), + }, + { + provide: QueueLoggerService, useFactory: () => ({ + logger: ({ + createSubLogger: () => ({ + info: jest.fn(), + warn: jest.fn(), + succ: jest.fn(), + }), + }), + }), + }, + ], + }) + .compile(); + + usersRepository = app.get(DI.usersRepository); + userProfilesRepository = app.get(DI.userProfilesRepository); + + service = app.get(CheckModeratorsActivityProcessorService); + idService = app.get(IdService); + roleService = app.get(RoleService) as jest.Mocked<RoleService>; + announcementService = app.get(AnnouncementService) as jest.Mocked<AnnouncementService>; + emailService = app.get(EmailService) as jest.Mocked<EmailService>; + systemWebhookService = app.get(SystemWebhookService) as jest.Mocked<SystemWebhookService>; + + app.enableShutdownHooks(); + }); + + beforeEach(async () => { + clock = lolex.install({ + now: new Date(baseDate), + shouldClearNativeTimers: true, + }); + + systemWebhook1 = crateSystemWebhook({ on: ['inactiveModeratorsWarning'] }); + systemWebhook2 = crateSystemWebhook({ on: ['inactiveModeratorsWarning', 'inactiveModeratorsInvitationOnlyChanged'] }); + systemWebhook3 = crateSystemWebhook({ on: ['abuseReport'] }); + + emailService.sendEmail.mockReturnValue(Promise.resolve()); + announcementService.create.mockReturnValue(Promise.resolve({} as never)); + systemWebhookService.fetchActiveSystemWebhooks.mockResolvedValue([systemWebhook1, systemWebhook2, systemWebhook3]); + systemWebhookService.enqueueSystemWebhook.mockReturnValue(Promise.resolve({} as never)); + }); + + afterEach(async () => { + clock.uninstall(); + await usersRepository.delete({}); + await userProfilesRepository.delete({}); + roleService.getModerators.mockReset(); + announcementService.create.mockReset(); + emailService.sendEmail.mockReset(); + systemWebhookService.enqueueSystemWebhook.mockReset(); + }); + + afterAll(async () => { + await app.close(); + }); + + // -------------------------------------------------------------------------------------- + + describe('evaluateModeratorsInactiveDays', () => { + test('[isModeratorsInactive] inactiveãªãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒã„ã¦ã‚‚ä»–ã®ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿ãƒ¼ãŒã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªã‚‰"é‹å–¶ãŒéžã‚¢ã‚¯ãƒ†ã‚£ãƒ–"ã¨ã—ã¦ã¿ãªã•れãªã„', async () => { + const [user1, user2, user3, user4] = await Promise.all([ + // 期é™ã‚ˆã‚Šã‚‚1ç§’æ–°ã—ã„タイミングã§ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化(セーフ) + createUser({ lastActiveDate: subDays(addSeconds(baseDate, 1), 7) }), + // 期é™ã¡ã‚‡ã†ã©ã«ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化(セーフ) + createUser({ lastActiveDate: subDays(baseDate, 7) }), + // 期é™ã‚ˆã‚Šã‚‚1ç§’å¤ã„タイミングã§ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化(アウト) + createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }), + // 対象外 + createUser({ lastActiveDate: null }), + ]); + + mockModeratorRole([user1, user2, user3, user4]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user3]); + }); + + test('[isModeratorsInactive] 全員éžã‚¢ã‚¯ãƒ†ã‚£ãƒ–ãªã‚‰"é‹å–¶ãŒéžã‚¢ã‚¯ãƒ†ã‚£ãƒ–"ã¨ã—ã¦ã¿ãªã•れる', async () => { + const [user1, user2] = await Promise.all([ + // 期é™ã‚ˆã‚Šã‚‚1ç§’å¤ã„タイミングã§ã‚¢ã‚¯ãƒ†ã‚£ãƒ–化(アウト) + createUser({ lastActiveDate: subDays(subSeconds(baseDate, 1), 7) }), + // 対象外 + createUser({ lastActiveDate: null }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(true); + expect(result.inactiveModerators).toEqual([user1]); + }); + + test('[remainingTime] 猶予ã¾ã§24時間ã‚ã‚‹å ´åˆã€çŒ¶äºˆ1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã¾ã§æ®‹ã‚Š24時間->猶予1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(baseDate, 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.remainingTime.asDays).toBe(1); + expect(result.remainingTime.asHours).toBe(24); + }); + + test('[remainingTime] 猶予ã¾ã§25時間ã‚ã‚‹å ´åˆã€çŒ¶äºˆ1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã¾ã§æ®‹ã‚Š25時間->猶予1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(addHours(baseDate, 1), 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.remainingTime.asDays).toBe(1); + expect(result.remainingTime.asHours).toBe(25); + }); + + test('[remainingTime] 猶予ã¾ã§23時間ã‚ã‚‹å ´åˆã€çŒ¶äºˆ0æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã¾ã§æ®‹ã‚Š23時間->猶予0æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 6) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.remainingTime.asDays).toBe(0); + expect(result.remainingTime.asHours).toBe(23); + }); + + test('[remainingTime] 期é™ã¡ã‚‡ã†ã©ã®å ´åˆã€çŒ¶äºˆ0æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã¡ã‚‡ã†ã©->猶予0æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(baseDate, 7) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(false); + expect(result.inactiveModerators).toEqual([user1]); + expect(result.remainingTime.asDays).toBe(0); + expect(result.remainingTime.asHours).toBe(0); + }); + + test('[remainingTime] 期é™ã‚ˆã‚Š1時間超éŽã—ã¦ã„ã‚‹å ´åˆã€çŒ¶äºˆ-1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 8) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã‚ˆã‚Š1時間超éŽ->猶予-1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(subHours(baseDate, 1), 7) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(true); + expect(result.inactiveModerators).toEqual([user1, user2]); + expect(result.remainingTime.asDays).toBe(-1); + expect(result.remainingTime.asHours).toBe(-1); + }); + + test('[remainingTime] 期é™ã‚ˆã‚Š25時間超éŽã—ã¦ã„ã‚‹å ´åˆã€çŒ¶äºˆ-2æ—¥ã¨ã—ã¦è¨ˆç®—ã•れる', async () => { + const [user1, user2] = await Promise.all([ + createUser({ lastActiveDate: subDays(baseDate, 10) }), + // 猶予ã¯ã“ã®ãƒ¦ãƒ¼ã‚¶åŸºæº–ã§è¨ˆç®—ã•れる想定。 + // 期é™ã‚ˆã‚Š1時間超éŽ->猶予-1æ—¥ã¨ã—ã¦è¨ˆç®—ã•れるã¯ãšã§ã‚ã‚‹ + createUser({ lastActiveDate: subDays(subHours(baseDate, 25), 7) }), + ]); + + mockModeratorRole([user1, user2]); + + const result = await service.evaluateModeratorsInactiveDays(); + expect(result.isModeratorsInactive).toBe(true); + expect(result.inactiveModerators).toEqual([user1, user2]); + expect(result.remainingTime.asDays).toBe(-2); + expect(result.remainingTime.asHours).toBe(-25); + }); + }); + + describe('notifyInactiveModeratorsWarning', () => { + test('[notification + mail] 通知ã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿å…¨å“¡ã«ç™ºä¿¡ã•れã€ãƒ¡ãƒ¼ãƒ«ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒå˜åœ¨ï¼‹èªè¨¼æ¸ˆã¿ã®å ´åˆã®ã¿', async () => { + const [user1, user2, user3, user4, root] = await Promise.all([ + createUser({}, { email: 'user1@example.com', emailVerified: true }), + createUser({}, { email: 'user2@example.com', emailVerified: false }), + createUser({}, { email: null, emailVerified: false }), + createUser({}, { email: 'user4@example.com', emailVerified: true }), + createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }), + ]); + + mockModeratorRole([user1, user2, user3, root]); + await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 }); + + expect(emailService.sendEmail).toHaveBeenCalledTimes(2); + expect(emailService.sendEmail.mock.calls[0][0]).toBe('user1@example.com'); + expect(emailService.sendEmail.mock.calls[1][0]).toBe('root@example.com'); + }); + + test('[systemWebhook] "inactiveModeratorsWarning"ãŒæœ‰åйãªSystemWebhookã«å¯¾ã—ã¦é€ä¿¡ã•れる', async () => { + const [user1] = await Promise.all([ + createUser({}, { email: 'user1@example.com', emailVerified: true }), + ]); + + mockModeratorRole([user1]); + await service.notifyInactiveModeratorsWarning({ time: 1, asDays: 0, asHours: 0 }); + + expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(2); + expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook1); + expect(systemWebhookService.enqueueSystemWebhook.mock.calls[1][0]).toEqual(systemWebhook2); + }); + }); + + describe('notifyChangeToInvitationOnly', () => { + test('[notification + mail] 通知ã¯ãƒ¢ãƒ‡ãƒ¬ãƒ¼ã‚¿å…¨å“¡ã«ç™ºä¿¡ã•れã€ãƒ¡ãƒ¼ãƒ«ã¯ãƒ¡ãƒ¼ãƒ«ã‚¢ãƒ‰ãƒ¬ã‚¹ãŒå˜åœ¨ï¼‹èªè¨¼æ¸ˆã¿ã®å ´åˆã®ã¿', async () => { + const [user1, user2, user3, user4, root] = await Promise.all([ + createUser({}, { email: 'user1@example.com', emailVerified: true }), + createUser({}, { email: 'user2@example.com', emailVerified: false }), + createUser({}, { email: null, emailVerified: false }), + createUser({}, { email: 'user4@example.com', emailVerified: true }), + createUser({ isRoot: true }, { email: 'root@example.com', emailVerified: true }), + ]); + + mockModeratorRole([user1, user2, user3, root]); + await service.notifyChangeToInvitationOnly(); + + expect(announcementService.create).toHaveBeenCalledTimes(4); + expect(announcementService.create.mock.calls[0][0].userId).toBe(user1.id); + expect(announcementService.create.mock.calls[1][0].userId).toBe(user2.id); + expect(announcementService.create.mock.calls[2][0].userId).toBe(user3.id); + expect(announcementService.create.mock.calls[3][0].userId).toBe(root.id); + + expect(emailService.sendEmail).toHaveBeenCalledTimes(2); + expect(emailService.sendEmail.mock.calls[0][0]).toBe('user1@example.com'); + expect(emailService.sendEmail.mock.calls[1][0]).toBe('root@example.com'); + }); + + test('[systemWebhook] "inactiveModeratorsInvitationOnlyChanged"ãŒæœ‰åйãªSystemWebhookã«å¯¾ã—ã¦é€ä¿¡ã•れる', async () => { + const [user1] = await Promise.all([ + createUser({}, { email: 'user1@example.com', emailVerified: true }), + ]); + + mockModeratorRole([user1]); + await service.notifyChangeToInvitationOnly(); + + expect(systemWebhookService.enqueueSystemWebhook).toHaveBeenCalledTimes(1); + expect(systemWebhookService.enqueueSystemWebhook.mock.calls[0][0]).toEqual(systemWebhook2); + }); + }); +}); diff --git a/packages/frontend-embed/package.json b/packages/frontend-embed/package.json index 021a63068a..528faf0a85 100644 --- a/packages/frontend-embed/package.json +++ b/packages/frontend-embed/package.json @@ -15,58 +15,58 @@ "@phosphor-icons/web": "^2.0.3", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.2", + "@rollup/pluginutils": "5.1.3", "@transfem-org/sfm-js": "0.24.5", "@twemoji/parser": "15.1.1", - "@vitejs/plugin-vue": "5.1.4", - "@vue/compiler-sfc": "3.5.10", + "@vitejs/plugin-vue": "5.2.0", + "@vue/compiler-sfc": "3.5.12", "astring": "1.9.0", "buraha": "0.0.1", "estree-walker": "3.0.3", "misskey-js": "workspace:*", "frontend-shared": "workspace:*", "punycode": "2.3.1", - "rollup": "4.22.5", - "sass": "1.79.3", - "shiki": "1.12.0", + "rollup": "4.26.0", + "sass": "1.79.4", + "shiki": "1.22.2", "tinycolor2": "1.6.0", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", - "typescript": "5.6.2", + "typescript": "5.6.3", "uuid": "10.0.0", "json5": "2.2.3", - "vite": "5.4.8", - "vue": "3.5.10" + "vite": "5.4.11", + "vue": "3.5.12" }, "devDependencies": { "@misskey-dev/summaly": "5.1.0", "@testing-library/vue": "8.1.0", "@types/estree": "1.0.6", "@types/micromatch": "4.0.9", - "@types/node": "20.14.12", + "@types/node": "22.9.0", "@types/punycode": "2.1.4", "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", - "@types/ws": "8.5.12", + "@types/ws": "8.5.13", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "@vitest/coverage-v8": "1.6.0", - "@vue/runtime-core": "3.5.10", - "acorn": "8.12.1", + "@vue/runtime-core": "3.5.12", + "acorn": "8.14.0", "cross-env": "7.0.3", - "eslint-plugin-import": "2.30.0", - "eslint-plugin-vue": "9.28.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-vue": "9.31.0", "fast-glob": "3.3.2", "happy-dom": "10.0.3", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.3.4", + "msw": "2.6.4", "nodemon": "3.1.7", "prettier": "3.3.3", "start-server-and-test": "2.0.8", "vite-plugin-turbosnap": "1.0.3", - "vue-component-type-helpers": "2.1.6", + "vue-component-type-helpers": "2.1.10", "vue-eslint-parser": "9.4.3", - "vue-tsc": "2.1.6" + "vue-tsc": "2.1.10" } } diff --git a/packages/frontend-embed/src/boot.ts b/packages/frontend-embed/src/boot.ts index 7a16efe4ab..71a3156311 100644 --- a/packages/frontend-embed/src/boot.ts +++ b/packages/frontend-embed/src/boot.ts @@ -19,6 +19,7 @@ import { url } from '@@/js/config.js'; import { parseEmbedParams } from '@@/js/embed-page.js'; import { postMessageToParentWindow, setIframeId } from '@/post-message.js'; import { serverContext } from '@/server-context.js'; +import { i18n } from '@/i18n.js'; import type { Theme } from '@/theme.js'; @@ -125,6 +126,27 @@ window.onunhandledrejection = null; removeSplash(); +//#region Self-XSS 対ç–メッセージ +console.log( + `%c${i18n.ts._selfXssPrevention.warning}`, + 'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;', +); +console.log( + `%c${i18n.ts._selfXssPrevention.title}`, + 'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;', +); +console.log( + `%c${i18n.ts._selfXssPrevention.description1}`, + 'font-size: 16px; font-weight: 700;', +); +console.log( + `%c${i18n.ts._selfXssPrevention.description2}`, + 'font-size: 16px;', + 'font-size: 20px; font-weight: 700; color: #f00;', +); +console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' })); +//#endregion + function removeSplash() { const splash = document.getElementById('splash'); if (splash) { diff --git a/packages/frontend-embed/src/components/EmCustomEmoji.vue b/packages/frontend-embed/src/components/EmCustomEmoji.vue index e4149cf363..59b670cdc6 100644 --- a/packages/frontend-embed/src/components/EmCustomEmoji.vue +++ b/packages/frontend-embed/src/components/EmCustomEmoji.vue @@ -38,8 +38,6 @@ const props = defineProps<{ host?: string | null; url?: string; useOriginalSize?: boolean; - menu?: boolean; - menuReaction?: boolean; fallbackToImage?: boolean; }>(); diff --git a/packages/frontend-embed/src/components/EmLoading.vue b/packages/frontend-embed/src/components/EmLoading.vue index 49d8ace37b..47d797606b 100644 --- a/packages/frontend-embed/src/components/EmLoading.vue +++ b/packages/frontend-embed/src/components/EmLoading.vue @@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{ --size: 38px; &.colored { - color: var(--accent); + color: var(--MI_THEME-accent); } &.inline { diff --git a/packages/frontend-embed/src/components/EmMediaBanner.vue b/packages/frontend-embed/src/components/EmMediaBanner.vue index bcf8a23a7d..24874940f3 100644 --- a/packages/frontend-embed/src/components/EmMediaBanner.vue +++ b/packages/frontend-embed/src/components/EmMediaBanner.vue @@ -32,17 +32,17 @@ defineProps<{ display: flex; align-items: center; width: 100%; - padding: var(--margin); + padding: var(--MI-margin); margin-top: 4px; - border: 1px solid var(--inputBorder); - border-radius: var(--radius); - background-color: var(--panel); + border: 1px solid var(--MI_THEME-inputBorder); + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); transition: background-color .1s, border-color .1s; &:hover { text-decoration: none; - border-color: var(--inputBorderHover); - background-color: var(--buttonHoverBg); + border-color: var(--MI_THEME-inputBorderHover); + background-color: var(--MI_THEME-buttonHoverBg); } } diff --git a/packages/frontend-embed/src/components/EmMediaImage.vue b/packages/frontend-embed/src/components/EmMediaImage.vue index 076386e876..3bdf702b01 100644 --- a/packages/frontend-embed/src/components/EmMediaImage.vue +++ b/packages/frontend-embed/src/components/EmMediaImage.vue @@ -36,7 +36,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.indicators"> <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> <div v-if="image.comment" :class="$style.indicator">ALT</div> - <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> + <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> </div> <i v-if="!hide" class="ti ti-eye-off" :class="$style.hide" @click.stop="hide = true"></i> </div> @@ -94,8 +94,8 @@ async function onclick(ev: MouseEvent) { display: block; position: absolute; border-radius: 6px; - background-color: var(--fg); - color: var(--accentLighten); + background-color: var(--MI_THEME-fg); + color: var(--MI_THEME-accentLighten); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -114,19 +114,19 @@ async function onclick(ev: MouseEvent) { .visible { position: relative; - //box-shadow: 0 0 0 1px var(--divider) inset; - background: var(--bg); + //box-shadow: 0 0 0 1px var(--MI_THEME-divider) inset; + background: var(--MI_THEME-bg); background-size: 16px 16px; } html[data-color-scheme=dark] .visible { --c: rgb(255 255 255 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } html[data-color-scheme=light] .visible { --c: rgb(0 0 0 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } .imageContainer { @@ -150,10 +150,10 @@ html[data-color-scheme=light] .visible { } .indicator { - /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: 6px; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend-embed/src/components/EmMediaVideo.vue b/packages/frontend-embed/src/components/EmMediaVideo.vue index ce751f9acd..e2779bdee4 100644 --- a/packages/frontend-embed/src/components/EmMediaVideo.vue +++ b/packages/frontend-embed/src/components/EmMediaVideo.vue @@ -29,9 +29,9 @@ defineProps<{ width: 100%; height: auto; aspect-ratio: 16 / 9; - padding: var(--margin); - border: 1px solid var(--divider); - border-radius: var(--radius); + padding: var(--MI-margin); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); background-color: #000; &:hover { @@ -49,7 +49,7 @@ defineProps<{ } .videoOverlayPlayButton { - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff; padding: 1rem; border-radius: 99rem; diff --git a/packages/frontend-embed/src/components/EmMention.vue b/packages/frontend-embed/src/components/EmMention.vue index a631783507..a71364237d 100644 --- a/packages/frontend-embed/src/components/EmMention.vue +++ b/packages/frontend-embed/src/components/EmMention.vue @@ -27,7 +27,7 @@ const canonical = props.host === localHost ? `@${props.username}` : `@${props.us const url = `/${canonical}`; -const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--mention')); +const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-mention')); bg.setAlpha(0.1); const bgCss = bg.toRgbString(); </script> @@ -37,7 +37,7 @@ const bgCss = bg.toRgbString(); display: inline-block; padding: 4px 8px 4px 4px; border-radius: 999px; - color: var(--mention); + color: var(--MI_THEME-mention); } .host { diff --git a/packages/frontend-embed/src/components/EmMfm.ts b/packages/frontend-embed/src/components/EmMfm.ts index 40a09df939..40189133d2 100644 --- a/packages/frontend-embed/src/components/EmMfm.ts +++ b/packages/frontend-embed/src/components/EmMfm.ts @@ -6,6 +6,7 @@ import { VNode, h, SetupContext, provide } from 'vue'; import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; +import { host } from '@@/js/config.js'; import EmUrl from '@/components/EmUrl.vue'; import EmTime from '@/components/EmTime.vue'; import EmLink from '@/components/EmLink.vue'; @@ -13,7 +14,6 @@ import EmMention from '@/components/EmMention.vue'; import EmEmoji from '@/components/EmEmoji.vue'; import EmCustomEmoji from '@/components/EmCustomEmoji.vue'; import EmA from '@/components/EmA.vue'; -import { host } from '@@/js/config.js'; function safeParseFloat(str: unknown): number | null { if (typeof str !== 'string' || str === '') return null; @@ -26,8 +26,8 @@ const QUOTE_STYLE = ` display: block; margin: 8px; padding: 6px 0 6px 12px; -color: var(--fg); -border-left: solid 3px var(--fg); +color: var(--MI_THEME-fg); +border-left: solid 3px var(--MI_THEME-fg); opacity: 0.7; `.split('\n').join(' '); @@ -41,9 +41,7 @@ type MfmProps = { rootScale?: number; nyaize?: boolean | 'respect'; parsedNodes?: mfm.MfmNode[] | null; - enableEmojiMenu?: boolean; - enableEmojiMenuReaction?: boolean; - linkNavigationBehavior?: string; + isBlock?: boolean; }; type MfmEvents = { @@ -52,8 +50,6 @@ type MfmEvents = { // eslint-disable-next-line import/no-default-export export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) { - provide('linkNavigationBehavior', props.linkNavigationBehavior); - const isNote = props.isNote ?? true; const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat; @@ -287,7 +283,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven } case 'border': { let color = validColor(token.props.args.color); - color = color ? `#${color}` : 'var(--accent)'; + color = color ? `#${color}` : 'var(--MI_THEME-accent)'; let b_style = token.props.args.style; if ( typeof b_style !== 'string' || @@ -320,7 +316,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven const child = token.children[0]; const unixtime = parseInt(child.type === 'text' ? child.props.text : ''); return h('span', { - style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: 999px; padding: 4px 10px 4px 6px;', + style: 'display: inline-block; font-size: 90%; border: solid 1px var(--MI_THEME-divider); border-radius: 999px; padding: 4px 10px 4px 6px;', }, [ h('i', { class: 'ti ti-clock', @@ -391,7 +387,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven return [h('bdi', h(EmA, { key: Math.random(), to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, - style: 'color:var(--hashtag);', + style: 'color:var(--MI_THEME-hashtag);', }, `#${token.props.hashtag}`))]; } @@ -428,8 +424,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven normal: props.plain, host: null, useOriginalSize: scale >= 2.5, - menu: props.enableEmojiMenu, - menuReaction: props.enableEmojiMenuReaction, fallbackToImage: false, })]; } else { @@ -453,8 +447,6 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven return [h(EmEmoji, { key: Math.random(), emoji: token.props.emoji, - menu: props.enableEmojiMenu, - menuReaction: props.enableEmojiMenuReaction, })]; } diff --git a/packages/frontend-embed/src/components/EmNote.vue b/packages/frontend-embed/src/components/EmNote.vue index 4677284747..025c4c0734 100644 --- a/packages/frontend-embed/src/components/EmNote.vue +++ b/packages/frontend-embed/src/components/EmNote.vue @@ -61,8 +61,6 @@ SPDX-License-Identifier: AGPL-3.0-only :author="appearNote.user" :nyaize="'respect'" :emojiUrls="appearNote.emojis" - :enableEmojiMenu="!true" - :enableEmojiMenuReaction="true" :isBlock="true" /> </div> @@ -109,6 +107,8 @@ SPDX-License-Identifier: AGPL-3.0-only import { computed, inject, ref, shallowRef } from 'vue'; import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { url } from '@@/js/config.js'; import I18n from '@/components/I18n.vue'; import EmNoteSub from '@/components/EmNoteSub.vue'; import EmNoteHeader from '@/components/EmNoteHeader.vue'; @@ -124,8 +124,6 @@ import EmUserName from '@/components/EmUserName.vue'; import EmTime from '@/components/EmTime.vue'; import { userPage } from '@/utils.js'; import { i18n } from '@/i18n.js'; -import { shouldCollapsed } from '@@/js/collapsed.js'; -import { url } from '@@/js/config.js'; function getAppearNote(note: Misskey.entities.Note) { return Misskey.note.isPureRenote(note) ? note.renote : note; @@ -165,14 +163,8 @@ const isDeleted = ref(false); font-size: 1.05em; overflow: clip; contain: content; - - // ã“ã‚Œã‚‰ã®æŒ‡å®šã¯ãƒ‘フォーマンスå‘上ã«ã¯æœ‰åйã ãŒã€ãƒŽãƒ¼ãƒˆã®é«˜ã•ã¯ä¸€å®šã§ãªã„ãŸã‚〠- // ä¸‹ã®æ–¹ã¾ã§ã‚¹ã‚¯ãƒãƒ¼ãƒ«ã™ã‚‹ã¨ä¸Šã®ãƒŽãƒ¼ãƒˆã®é«˜ã•ãŒã“ã“ã§æ±ºã‚打ã¡ã•れãŸã‚‚ã®ã«å¤‰åŒ–ã—ã€è¡¨ç¤ºã—ã¦ã„るノートã®ä½ç½®ãŒå¤‰ã‚ã£ã¦ã—ã¾ã† - // ノートãŒãƒžã‚¦ãƒ³ãƒˆã•れãŸã¨ãã«è‡ªèº«ã®é«˜ã•ã‚’å–å¾—ã— contain-intrinsic-size ã‚’è¨å®šã—ãªãŠã›ã°ã»ã¼è§£æ±ºã§ããã†ã ãŒã€ - // 今度ã¯ãã®å‡¦ç†è‡ªä½“ãŒãƒ‘フォーマンス低下ã®åŽŸå› ã«ãªã‚‰ãªã„ã‹æ‡¸å¿µã•れる。ã¾ãŸã€è¢«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã§ã‚‚高ã•ã¯å¤‰åŒ–ã™ã‚‹ãŸã‚ã€ã‚„ã¯ã‚Šå¤šå°‘ã®ã‚ºãƒ¬ã¯ç”Ÿã˜ã‚‹ - // 一度レンダリングã•れãŸè¦ç´ ã¯ãƒ–ラウザãŒã‚ˆã—ãªã«ã‚µã‚¤ã‚ºã‚’覚ãˆã¦ãŠã„ã¦ãれるよã†ãªå®Ÿè£…ã«ãªã‚‹ã¾ã§å¾…ã£ãŸæ–¹ãŒè‰¯ã•ãã†(ãªã‚‹ã®ã‹ï¼Ÿ) - //content-visibility: auto; - //contain-intrinsic-size: 0 128px; + content-visibility: auto; + contain-intrinsic-size: 0 150px; &:focus-visible { outline: none; @@ -190,8 +182,8 @@ const isDeleted = ref(false); margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: dashed 2px var(--focus); - border-radius: var(--radius); + border: dashed 2px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -213,9 +205,9 @@ const isDeleted = ref(false); right: 12px; padding: 0 4px; margin-bottom: 0 !important; - background: var(--popup); + background: var(--MI_THEME-popup); border-radius: 8px; - box-shadow: 0px 4px 32px var(--shadow); + box-shadow: 0px 4px 32px var(--MI_THEME-shadow); } .footerButton { @@ -260,7 +252,7 @@ const isDeleted = ref(false); padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); & + .article { padding-top: 8px; @@ -357,7 +349,7 @@ const isDeleted = ref(false); width: 58px; height: 58px; position: sticky !important; - top: calc(22px + var(--stickyTop, 0px)); + top: calc(22px + var(--MI-stickyTop, 0px)); left: 0; } @@ -378,12 +370,12 @@ const isDeleted = ref(false); width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -404,16 +396,16 @@ const isDeleted = ref(false); z-index: 2; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); &:hover > .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -425,13 +417,13 @@ const isDeleted = ref(false); } .replyIcon { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -450,7 +442,7 @@ const isDeleted = ref(false); .quoteNote { padding: 16px; - border: dashed 1px var(--renote); + border: dashed 1px var(--MI_THEME-renote); border-radius: 8px; overflow: clip; } @@ -474,7 +466,7 @@ const isDeleted = ref(false); } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -551,7 +543,7 @@ const isDeleted = ref(false); margin: 0 10px 0 0; width: 46px; height: 46px; - top: calc(14px + var(--stickyTop, 0px)); + top: calc(14px + var(--MI-stickyTop, 0px)); } } diff --git a/packages/frontend-embed/src/components/EmNoteDetailed.vue b/packages/frontend-embed/src/components/EmNoteDetailed.vue index 0068e05bc4..c4ea9b4f2e 100644 --- a/packages/frontend-embed/src/components/EmNoteDetailed.vue +++ b/packages/frontend-embed/src/components/EmNoteDetailed.vue @@ -196,7 +196,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); } .renoteAvatar { @@ -282,7 +282,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); padding: 4px 6px; font-size: 80%; line-height: 1; - border: solid 0.5px var(--divider); + border: solid 0.5px var(--MI_THEME-divider); border-radius: 4px; } @@ -324,14 +324,14 @@ const collapsed = ref(appearNote.value.cw == null && isLong); } .noteReplyTarget { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .rn { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .reactionOmitted { @@ -351,7 +351,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); .quoteNote { padding: 16px; - border: dashed 1px var(--renote); + border: dashed 1px var(--MI_THEME-renote); border-radius: 8px; overflow: clip; } @@ -365,12 +365,12 @@ const collapsed = ref(appearNote.value.cw == null && isLong); width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -391,16 +391,16 @@ const collapsed = ref(appearNote.value.cw == null && isLong); z-index: 2; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), var(--X15)); + background: linear-gradient(0deg, var(--MI_THEME-panel), var(--MI_THEME-X15)); &:hover > .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -423,7 +423,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -439,7 +439,7 @@ const collapsed = ref(appearNote.value.cw == null && isLong); opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } diff --git a/packages/frontend-embed/src/components/EmNoteHeader.vue b/packages/frontend-embed/src/components/EmNoteHeader.vue index 7d0b9bacad..85b4aac071 100644 --- a/packages/frontend-embed/src/components/EmNoteHeader.vue +++ b/packages/frontend-embed/src/components/EmNoteHeader.vue @@ -72,7 +72,7 @@ defineProps<{ margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 0.5px var(--divider); + border: solid 0.5px var(--MI_THEME-divider); border-radius: 3px; } diff --git a/packages/frontend-embed/src/components/EmNoteSimple.vue b/packages/frontend-embed/src/components/EmNoteSimple.vue index d4afc0b4d7..83e73f9870 100644 --- a/packages/frontend-embed/src/components/EmNoteSimple.vue +++ b/packages/frontend-embed/src/components/EmNoteSimple.vue @@ -53,7 +53,7 @@ const showContent = ref(false); height: 34px; border-radius: 8px; position: sticky !important; - top: calc(16px + var(--stickyTop, 0px)); + top: calc(16px + var(--MI-stickyTop, 0px)); left: 0; } diff --git a/packages/frontend-embed/src/components/EmNoteSub.vue b/packages/frontend-embed/src/components/EmNoteSub.vue index 9fbd1eae35..cc379e8281 100644 --- a/packages/frontend-embed/src/components/EmNoteSub.vue +++ b/packages/frontend-embed/src/components/EmNoteSub.vue @@ -123,7 +123,7 @@ if (props.detail) { } .reply, .more { - border-left: solid 0.5px var(--divider); + border-left: solid 0.5px var(--MI_THEME-divider); margin-top: 10px; } @@ -144,7 +144,7 @@ if (props.detail) { .muted { text-align: center; padding: 8px !important; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); margin: 8px 8px 0 8px; border-radius: 8px; } diff --git a/packages/frontend-embed/src/components/EmNotes.vue b/packages/frontend-embed/src/components/EmNotes.vue index 3418d97f77..4211261e19 100644 --- a/packages/frontend-embed/src/components/EmNotes.vue +++ b/packages/frontend-embed/src/components/EmNotes.vue @@ -43,10 +43,10 @@ defineExpose({ <style lang="scss" module> .root { - background: var(--panel); + background: var(--MI_THEME-panel); } .note { - border-bottom: 0.5px solid var(--divider); + border-bottom: 0.5px solid var(--MI_THEME-divider); } </style> diff --git a/packages/frontend-embed/src/components/EmPoll.vue b/packages/frontend-embed/src/components/EmPoll.vue index a2b1203449..d197e094c6 100644 --- a/packages/frontend-embed/src/components/EmPoll.vue +++ b/packages/frontend-embed/src/components/EmPoll.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <li v-for="(choice, i) in poll.choices" :key="i" :class="$style.choice"> <div :class="$style.bg" :style="{ 'width': `${choice.votes / total * 100}%` }"></div> <span :class="$style.fg"> - <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template> + <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--MI_THEME-accent);"></i></template> <EmMfm :text="choice.text" :plain="true"/> <span style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span> </span> @@ -52,8 +52,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); position: relative; margin: 4px 0; padding: 4px; - //border: solid 0.5px var(--divider); - background: var(--accentedBg); + //border: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-accentedBg); border-radius: 4px; overflow: clip; } @@ -63,8 +63,8 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); top: 0; left: 0; height: 100%; - background: var(--accent); - background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); + background: var(--MI_THEME-accent); + background: linear-gradient(90deg,var(--MI_THEME-buttonGradateA),var(--MI_THEME-buttonGradateB)); transition: width 1s ease; } @@ -72,11 +72,11 @@ const total = computed(() => sum(props.poll.choices.map(x => x.votes))); position: relative; display: inline-block; padding: 3px 5px; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 3px; } .info { - color: var(--fg); + color: var(--MI_THEME-fg); } </style> diff --git a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue index 2e43eb8d17..2ebff489fd 100644 --- a/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue +++ b/packages/frontend-embed/src/components/EmReactionsViewer.reaction.vue @@ -38,7 +38,7 @@ const props = defineProps<{ justify-content: center; &.canToggle { - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); &:hover { background: rgba(0, 0, 0, 0.1); @@ -72,12 +72,12 @@ const props = defineProps<{ } &.reacted, &.reacted:hover { - background: var(--accentedBg); - color: var(--accent); - box-shadow: 0 0 0 1px var(--accent) inset; + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + box-shadow: 0 0 0 1px var(--MI_THEME-accent) inset; > .count { - color: var(--accent); + color: var(--MI_THEME-accent); } > .icon { diff --git a/packages/frontend-embed/src/components/EmSubNoteContent.vue b/packages/frontend-embed/src/components/EmSubNoteContent.vue index f433573df5..e9acbcb293 100644 --- a/packages/frontend-embed/src/components/EmSubNoteContent.vue +++ b/packages/frontend-embed/src/components/EmSubNoteContent.vue @@ -65,11 +65,11 @@ const collapsed = ref(isLong); left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; @@ -78,7 +78,7 @@ const collapsed = ref(isLong); &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } @@ -87,25 +87,25 @@ const collapsed = ref(isLong); .reply { margin-right: 6px; - color: var(--accent); + color: var(--MI_THEME-accent); } .rp { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .showLess { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) + 14px); + bottom: calc(var(--MI-stickyBottom, 0px) + 14px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; border-radius: 999px; diff --git a/packages/frontend-embed/src/components/EmTime.vue b/packages/frontend-embed/src/components/EmTime.vue index c3986f7d70..7902e18483 100644 --- a/packages/frontend-embed/src/components/EmTime.vue +++ b/packages/frontend-embed/src/components/EmTime.vue @@ -98,10 +98,10 @@ if (!invalid && props.origin === null && (props.mode === 'relative' || props.mod <style lang="scss" module> .old1 { - color: var(--warn); + color: var(--MI_THEME-warn); } .old1.old2 { - color: var(--error); + color: var(--MI_THEME-error); } </style> diff --git a/packages/frontend-embed/src/components/EmTimelineContainer.vue b/packages/frontend-embed/src/components/EmTimelineContainer.vue index 6c30b1102d..60fd67ced9 100644 --- a/packages/frontend-embed/src/components/EmTimelineContainer.vue +++ b/packages/frontend-embed/src/components/EmTimelineContainer.vue @@ -20,7 +20,7 @@ withDefaults(defineProps<{ <style module lang="scss"> .timelineRoot { - background-color: var(--panel); + background-color: var(--MI_THEME-panel); height: 100%; max-height: var(--embedMaxHeight, none); display: flex; @@ -29,7 +29,7 @@ withDefaults(defineProps<{ .header { flex-shrink: 0; - border-bottom: 1px solid var(--divider); + border-bottom: 1px solid var(--MI_THEME-divider); } .body { diff --git a/packages/frontend-embed/src/pages/clip.vue b/packages/frontend-embed/src/pages/clip.vue index 2528dc4b80..f4d4e8cf6f 100644 --- a/packages/frontend-embed/src/pages/clip.vue +++ b/packages/frontend-embed/src/pages/clip.vue @@ -100,7 +100,7 @@ function top(ev: MouseEvent) { display: flex; min-width: 0; align-items: center; - gap: var(--margin); + gap: var(--MI-margin); overflow: hidden; .headerClipIconRoot { @@ -110,8 +110,8 @@ function top(ev: MouseEvent) { line-height: 32px; font-size: 14px; text-align: center; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); border-radius: 50%; } diff --git a/packages/frontend-embed/src/pages/note.vue b/packages/frontend-embed/src/pages/note.vue index 6f6c8c0f63..e879430286 100644 --- a/packages/frontend-embed/src/pages/note.vue +++ b/packages/frontend-embed/src/pages/note.vue @@ -47,6 +47,6 @@ if (note.value?.url != null || note.value?.uri != null) { <style lang="scss" module> .noteEmbedRoot { - background-color: var(--panel); + background-color: var(--MI_THEME-panel); } </style> diff --git a/packages/frontend-embed/src/pages/tag.vue b/packages/frontend-embed/src/pages/tag.vue index b481b3ebe5..4b00ae7c2d 100644 --- a/packages/frontend-embed/src/pages/tag.vue +++ b/packages/frontend-embed/src/pages/tag.vue @@ -83,7 +83,7 @@ function top(ev: MouseEvent) { display: flex; min-width: 0; align-items: center; - gap: var(--margin); + gap: var(--MI-margin); overflow: hidden; .headerClipIconRoot { @@ -93,8 +93,8 @@ function top(ev: MouseEvent) { line-height: 32px; font-size: 14px; text-align: center; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); border-radius: 50%; } diff --git a/packages/frontend-embed/src/pages/user-timeline.vue b/packages/frontend-embed/src/pages/user-timeline.vue index 85e6f52d50..348b1a7622 100644 --- a/packages/frontend-embed/src/pages/user-timeline.vue +++ b/packages/frontend-embed/src/pages/user-timeline.vue @@ -117,7 +117,7 @@ function top(ev: MouseEvent) { display: flex; min-width: 0; align-items: center; - gap: var(--margin); + gap: var(--MI-margin); overflow: hidden; .avatarLink { diff --git a/packages/frontend-embed/src/style.scss b/packages/frontend-embed/src/style.scss index 979e959f52..6097281c37 100644 --- a/packages/frontend-embed/src/style.scss +++ b/packages/frontend-embed/src/style.scss @@ -7,18 +7,18 @@ */ :root { - --radius: 12px; - --marginFull: 14px; - --marginHalf: 10px; + --MI-radius: 12px; + --MI-marginFull: 14px; + --MI-marginHalf: 10px; - --margin: var(--marginFull); + --MI-margin: var(--MI-marginFull); } html { background-color: transparent; color-scheme: light dark; - color: var(--fg); - accent-color: var(--accent); + color: var(--MI_THEME-fg); + accent-color: var(--MI_THEME-accent); overflow: clip; overflow-wrap: break-word; font-family: 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; @@ -29,7 +29,7 @@ html { -webkit-text-size-adjust: 100%; &, * { - scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent; scrollbar-width: thin; &::-webkit-scrollbar { @@ -42,14 +42,14 @@ html { } &::-webkit-scrollbar-thumb { - background: var(--scrollbarHandle); + background: var(--MI_THEME-scrollbarHandle); &:hover { - background: var(--scrollbarHandleHover); + background: var(--MI_THEME-scrollbarHandleHover); } &:active { - background: var(--accent); + background: var(--MI_THEME-accent); } } } @@ -93,7 +93,7 @@ rt { } :focus-visible { - outline: var(--focus) solid 2px; + outline: var(--MI_THEME-focus) solid 2px; outline-offset: -2px; &:hover { @@ -151,38 +151,38 @@ rt { ._buttonGray { @extend ._button; - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); &:not(:disabled):hover { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } } ._buttonPrimary { @extend ._button; - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:not(:disabled):hover { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } &:not(:disabled):active { - background: hsl(from var(--accent) h s calc(l - 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 5)); } } ._buttonGradate { @extend ._buttonPrimary; - color: var(--fgOnAccent); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } &:not(:disabled):active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } @@ -199,13 +199,13 @@ rt { } ._help { - color: var(--accent); + color: var(--MI_THEME-accent); cursor: help; } ._textButton { @extend ._button; - color: var(--accent); + color: var(--MI_THEME-accent); &:focus-visible { outline-offset: 2px; @@ -217,13 +217,13 @@ rt { } ._panel { - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); overflow: clip; } ._margin { - margin: var(--margin) 0; + margin: var(--MI-margin) 0; } ._gaps_m { @@ -241,7 +241,7 @@ rt { ._gaps { display: flex; flex-direction: column; - gap: var(--margin); + gap: var(--MI-margin); } ._buttons { @@ -263,24 +263,24 @@ rt { padding: 10px; box-sizing: border-box; text-align: center; - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); &:active { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } } ._popup { - background: var(--popup); - border-radius: var(--radius); + background: var(--MI_THEME-popup); + border-radius: var(--MI-radius); contain: content; } ._acrylic { - background: var(--acrylicPanel); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-acrylicPanel); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } ._fullinfo { @@ -296,7 +296,7 @@ rt { } ._link { - color: var(--link); + color: var(--MI_THEME-link); } ._caption { diff --git a/packages/frontend-embed/src/theme.ts b/packages/frontend-embed/src/theme.ts index 23e70cd0d3..4664ad4880 100644 --- a/packages/frontend-embed/src/theme.ts +++ b/packages/frontend-embed/src/theme.ts @@ -61,7 +61,7 @@ export function applyTheme(theme: Theme, persist = true) { } for (const [k, v] of Object.entries(props)) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } // iframeã‚’æ£å¸¸ã«é€éŽã•ã›ã‚‹ãŸã‚ã«ã€cssã®color-scheme㯠`light dark;` 固定ã«ã—ã¦ã‚る。style.scsså‚ç…§ diff --git a/packages/frontend-embed/src/ui.vue b/packages/frontend-embed/src/ui.vue index 8da5f46a96..4ba5968a91 100644 --- a/packages/frontend-embed/src/ui.vue +++ b/packages/frontend-embed/src/ui.vue @@ -88,14 +88,14 @@ onUnmounted(() => { <style lang="scss" module> .rootForEmbedPage { box-sizing: border-box; - border: 1px solid var(--divider); - background-color: var(--bg); + border: 1px solid var(--MI_THEME-divider); + background-color: var(--MI_THEME-bg); overflow: hidden; position: relative; height: auto; &.rounded { - border-radius: var(--radius); + border-radius: var(--MI-radius); } &.noBorder { diff --git a/packages/frontend-embed/vite.config.ts b/packages/frontend-embed/vite.config.ts index b95533c2cd..bf6f558893 100644 --- a/packages/frontend-embed/vite.config.ts +++ b/packages/frontend-embed/vite.config.ts @@ -90,6 +90,11 @@ export function getConfig(): UserConfig { return shortId + '-' + toBase62(hash(id)).substring(0, 4); }, }, + preprocessorOptions: { + scss: { + api: 'modern-compiler', + }, + }, }, define: { diff --git a/packages/frontend-shared/js/const.ts b/packages/frontend-shared/js/const.ts index a6eebe8e15..0dac166749 100644 --- a/packages/frontend-shared/js/const.ts +++ b/packages/frontend-shared/js/const.ts @@ -128,6 +128,7 @@ export const notificationTypes = [ 'roleAssigned', 'achievementEarned', 'exportCompleted', + 'login', 'test', 'app', 'edited', diff --git a/packages/frontend-shared/package.json b/packages/frontend-shared/package.json index 82fa62cb32..8bf25da161 100644 --- a/packages/frontend-shared/package.json +++ b/packages/frontend-shared/package.json @@ -21,12 +21,12 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "20.14.12", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", - "esbuild": "0.23.0", - "eslint-plugin-vue": "9.27.0", - "typescript": "5.5.4", + "esbuild": "0.24.0", + "eslint-plugin-vue": "9.31.0", + "typescript": "5.6.3", "vue-eslint-parser": "9.4.3" }, "files": [ @@ -34,6 +34,6 @@ ], "dependencies": { "misskey-js": "workspace:*", - "vue": "3.4.37" + "vue": "3.5.12" } } diff --git a/packages/frontend-shared/themes/_dark.json5 b/packages/frontend-shared/themes/_dark.json5 index e4649311c3..b3f1ab824b 100644 --- a/packages/frontend-shared/themes/_dark.json5 +++ b/packages/frontend-shared/themes/_dark.json5 @@ -30,7 +30,7 @@ panelHeaderBg: ':lighten<3<@panel', panelHeaderFg: '@fg', panelHeaderDivider: 'rgba(0, 0, 0, 0)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', thread: ':lighten<12<@panel', acrylicPanel: ':alpha<0.5<@panel', windowHeader: ':alpha<0.85<@panel', @@ -68,7 +68,6 @@ switchOnFg: '@accent', inputBorder: 'rgba(255, 255, 255, 0.1)', inputBorderHover: 'rgba(255, 255, 255, 0.2)', - listItemHoverBg: 'rgba(255, 255, 255, 0.03)', driveFolderBg: ':alpha<0.3<@accent', wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', badge: '#31b1ce', diff --git a/packages/frontend-shared/themes/_light.json5 b/packages/frontend-shared/themes/_light.json5 index b6218a5f1d..89c632b057 100644 --- a/packages/frontend-shared/themes/_light.json5 +++ b/packages/frontend-shared/themes/_light.json5 @@ -30,7 +30,7 @@ panelHeaderBg: ':lighten<3<@panel', panelHeaderFg: '@fg', panelHeaderDivider: 'rgba(0, 0, 0, 0)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', thread: ':darken<12<@panel', acrylicPanel: ':alpha<0.5<@panel', windowHeader: ':alpha<0.85<@panel', @@ -68,7 +68,6 @@ switchOnFg: '@fgOnAccent', inputBorder: 'rgba(0, 0, 0, 0.1)', inputBorderHover: 'rgba(0, 0, 0, 0.2)', - listItemHoverBg: 'rgba(0, 0, 0, 0.03)', driveFolderBg: ':alpha<0.3<@accent', wallpaperOverlay: 'rgba(255, 255, 255, 0.5)', badge: '#31b1ce', diff --git a/packages/frontend-shared/themes/d-astro.json5 b/packages/frontend-shared/themes/d-astro.json5 index a674a5c5c9..4422526a33 100644 --- a/packages/frontend-shared/themes/d-astro.json5 +++ b/packages/frontend-shared/themes/d-astro.json5 @@ -36,7 +36,7 @@ dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', inputBorderHover: 'rgba(255, 255, 255, 0.2)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', accentDarken: ':darken<10<@accent', acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', @@ -50,7 +50,6 @@ htmlThemeColor: '@bg', fgOnWhite: '@accent', panelHighlight: ':lighten<3<@panel', - listItemHoverBg: 'rgba(255, 255, 255, 0.03)', scrollbarHandle: 'rgba(255, 255, 255, 0.2)', wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', panelHeaderDivider: 'rgba(0, 0, 0, 0)', diff --git a/packages/frontend-shared/themes/d-u0.json5 b/packages/frontend-shared/themes/d-u0.json5 index 32ac9ec5cf..fb707c74c3 100644 --- a/packages/frontend-shared/themes/d-u0.json5 +++ b/packages/frontend-shared/themes/d-u0.json5 @@ -55,7 +55,7 @@ codeBoolean: '#c59eff', dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', accentDarken: ':darken<10<@accent', acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', @@ -69,7 +69,6 @@ buttonGradateB: ':hue<20<@accent', htmlThemeColor: '@bg', panelHighlight: ':lighten<3<@panel', - listItemHoverBg: 'rgba(255, 255, 255, 0.03)', scrollbarHandle: 'rgba(255, 255, 255, 0.2)', inputBorderHover: 'rgba(255, 255, 255, 0.2)', wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', diff --git a/packages/frontend-shared/themes/l-u0.json5 b/packages/frontend-shared/themes/l-u0.json5 index 0b952b003a..7062e7fe5b 100644 --- a/packages/frontend-shared/themes/l-u0.json5 +++ b/packages/frontend-shared/themes/l-u0.json5 @@ -56,7 +56,7 @@ codeBoolean: '#c59eff', dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', accentDarken: ':darken<10<@accent', acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', @@ -71,7 +71,6 @@ buttonGradateB: ':hue<20<@accent', htmlThemeColor: '@bg', panelHighlight: ':lighten<3<@panel', - listItemHoverBg: 'rgba(255, 255, 255, 0.03)', scrollbarHandle: '#74747433', inputBorderHover: 'rgba(255, 255, 255, 0.2)', wallpaperOverlay: 'rgba(0, 0, 0, 0.5)', diff --git a/packages/frontend-shared/themes/l-vivid.json5 b/packages/frontend-shared/themes/l-vivid.json5 index f1c63dde6e..39768d4ac6 100644 --- a/packages/frontend-shared/themes/l-vivid.json5 +++ b/packages/frontend-shared/themes/l-vivid.json5 @@ -39,7 +39,7 @@ dateLabelFg: '@fg', inputBorder: 'rgba(0, 0, 0, 0.1)', inputBorderHover: 'rgba(0, 0, 0, 0.2)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', accentDarken: ':darken<10<@accent', acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@accent', @@ -52,7 +52,6 @@ panelHeaderFg: '@fg', htmlThemeColor: '@bg', panelHighlight: ':darken<3<@panel', - listItemHoverBg: 'rgba(0, 0, 0, 0.03)', scrollbarHandle: 'rgba(0, 0, 0, 0.2)', wallpaperOverlay: 'rgba(255, 255, 255, 0.5)', fgTransparentWeak: ':alpha<0.75<@fg', diff --git a/packages/frontend-shared/themes/rosepine-dawn.json5 b/packages/frontend-shared/themes/rosepine-dawn.json5 index ff1ca0c996..6eb10fc724 100644 --- a/packages/frontend-shared/themes/rosepine-dawn.json5 +++ b/packages/frontend-shared/themes/rosepine-dawn.json5 @@ -23,7 +23,7 @@ panelHeaderBg: ':lighten<3<@panel', panelHeaderFg: '@fg', panelHeaderDivider: 'rgba(0, 0, 0, 0)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', acrylicPanel: ':alpha<0.5<@panel', popup: ':lighten<3<@panel', shadow: 'rgba(0, 0, 0, 0.1)', @@ -86,4 +86,4 @@ X17: ':alpha<0.8<@bg', }, author: '@thatonecalculator@stop.voring.me', -}
\ No newline at end of file +} diff --git a/packages/frontend-shared/themes/rosepine.json5 b/packages/frontend-shared/themes/rosepine.json5 index 06516f75fc..ae10bb6709 100644 --- a/packages/frontend-shared/themes/rosepine.json5 +++ b/packages/frontend-shared/themes/rosepine.json5 @@ -59,7 +59,7 @@ navHoverFg: ':lighten<17<@fg', dateLabelFg: '@fg', inputBorder: 'rgba(255, 255, 255, 0.1)', - panelBorder: '" solid 1px var(--divider)', + panelBorder: '" solid 1px var(--MI_THEME-divider)', accentDarken: ':darken<10<@accent', acrylicPanel: ':alpha<0.5<@panel', navIndicator: '@indicator', @@ -83,4 +83,4 @@ scrollbarHandleHover: 'rgba(255, 255, 255, 0.4)', }, author: '@thatonecalculator@stop.voring.me', -}
\ No newline at end of file +} diff --git a/packages/frontend/.storybook/generate.tsx b/packages/frontend/.storybook/generate.tsx index 42d1a10f0a..f2bdc631d2 100644 --- a/packages/frontend/.storybook/generate.tsx +++ b/packages/frontend/.storybook/generate.tsx @@ -397,7 +397,18 @@ function toStories(component: string): Promise<string> { const globs = await Promise.all([ glob('src/components/global/Mk*.vue'), glob('src/components/global/RouterView.vue'), - glob('src/components/Mk[A-E]*.vue'), + glob('src/components/MkAbuseReportWindow.vue'), + glob('src/components/MkAccountMoved.vue'), + glob('src/components/MkAchievements.vue'), + glob('src/components/MkAnalogClock.vue'), + glob('src/components/MkAnimBg.vue'), + glob('src/components/MkAnnouncementDialog.vue'), + glob('src/components/MkAntennaEditor.vue'), + glob('src/components/MkAntennaEditorDialog.vue'), + glob('src/components/MkAsUi.vue'), + glob('src/components/MkAutocomplete.vue'), + glob('src/components/MkAvatars.vue'), + glob('src/components/Mk[B-E]*.vue'), glob('src/components/MkFlashPreview.vue'), glob('src/components/MkGalleryPostPreview.vue'), glob('src/components/MkSignupServerRules.vue'), diff --git a/packages/frontend/assets/testcaptcha.png b/packages/frontend/assets/testcaptcha.png Binary files differnew file mode 100644 index 0000000000..9bfd252b51 --- /dev/null +++ b/packages/frontend/assets/testcaptcha.png diff --git a/packages/frontend/package.json b/packages/frontend/package.json index 58345d2eda..752b6cb388 100644 --- a/packages/frontend/package.json +++ b/packages/frontend/package.json @@ -24,29 +24,30 @@ "@phosphor-icons/web": "^2.0.3", "@rollup/plugin-json": "6.1.0", "@rollup/plugin-replace": "5.0.7", - "@rollup/pluginutils": "5.1.2", + "@rollup/pluginutils": "5.1.3", "@ruffle-rs/ruffle": "0.1.0-nightly.2024.10.15", "@syuilo/aiscript": "0.19.0", "@transfem-org/sfm-js": "0.24.5", "@twemoji/parser": "15.1.1", - "@vitejs/plugin-vue": "5.1.4", - "@vue/compiler-sfc": "3.5.10", + "@vitejs/plugin-vue": "5.2.0", + "@vue/compiler-sfc": "3.5.12", "aiscript-vscode": "github:aiscript-dev/aiscript-vscode#v0.1.11", "astring": "1.9.0", "broadcast-channel": "7.0.0", "buraha": "0.0.1", "canvas-confetti": "1.9.3", - "chart.js": "4.4.4", + "chart.js": "4.4.6", "chartjs-adapter-date-fns": "3.0.0", "chartjs-chart-matrix": "2.0.1", "chartjs-plugin-gradient": "0.6.1", "chartjs-plugin-zoom": "2.0.1", - "chromatic": "11.10.4", + "chromatic": "11.18.1", "compare-versions": "6.1.1", "cropperjs": "2.0.0-rc.2", "date-fns": "2.30.0", "estree-walker": "3.0.3", "eventemitter3": "5.0.1", + "frontend-shared": "workspace:*", "idb-keyval": "6.2.1", "insert-text-at-cursor": "0.3.0", "is-file-animated": "1.0.2", @@ -56,13 +57,12 @@ "misskey-bubble-game": "workspace:*", "misskey-js": "workspace:*", "misskey-reversi": "workspace:*", - "frontend-shared": "workspace:*", "photoswipe": "5.4.4", "punycode": "2.3.1", - "rollup": "4.22.5", - "sanitize-html": "2.13.0", + "rollup": "4.26.0", + "sanitize-html": "2.13.1", "sass": "1.79.3", - "shiki": "1.12.0", + "shiki": "1.22.2", "strict-event-emitter-types": "2.0.0", "textarea-caret": "3.1.0", "three": "0.169.0", @@ -70,76 +70,77 @@ "tinycolor2": "1.6.0", "tsc-alias": "1.8.10", "tsconfig-paths": "4.2.0", - "typescript": "5.6.2", + "typescript": "5.6.3", "uuid": "10.0.0", "v-code-diff": "1.13.1", - "vite": "5.4.8", - "vue": "3.5.10", + "vite": "5.4.11", + "vue": "3.5.12", "vuedraggable": "next" }, "optionalDependencies": { - "cypress": "13.15.0" + "cypress": "13.15.2" }, "devDependencies": { "@misskey-dev/summaly": "5.1.0", - "@storybook/addon-actions": "8.3.3", - "@storybook/addon-essentials": "8.3.3", - "@storybook/addon-interactions": "8.3.3", - "@storybook/addon-links": "8.3.3", - "@storybook/addon-mdx-gfm": "8.3.3", - "@storybook/addon-storysource": "8.3.3", - "@storybook/blocks": "8.3.3", - "@storybook/components": "8.3.3", - "@storybook/core-events": "8.3.3", - "@storybook/manager-api": "8.3.3", - "@storybook/preview-api": "8.3.3", - "@storybook/react": "8.3.3", - "@storybook/react-vite": "8.3.3", - "@storybook/test": "8.3.3", - "@storybook/theming": "8.3.3", - "@storybook/types": "8.3.3", - "@storybook/vue3": "8.3.3", - "@storybook/vue3-vite": "8.3.3", + "@storybook/addon-actions": "8.4.4", + "@storybook/addon-essentials": "8.4.4", + "@storybook/addon-interactions": "8.4.4", + "@storybook/addon-links": "8.4.4", + "@storybook/addon-mdx-gfm": "8.4.4", + "@storybook/addon-storysource": "8.4.4", + "@storybook/blocks": "8.4.4", + "@storybook/components": "8.4.4", + "@storybook/core-events": "8.4.4", + "@storybook/manager-api": "8.4.4", + "@storybook/preview-api": "8.4.4", + "@storybook/react": "8.4.4", + "@storybook/react-vite": "8.4.4", + "@storybook/test": "8.4.4", + "@storybook/theming": "8.4.4", + "@storybook/types": "8.4.4", + "@storybook/vue3": "8.4.4", + "@storybook/vue3-vite": "8.4.4", "@testing-library/vue": "8.1.0", + "@types/canvas-confetti": "^1.6.4", "@types/estree": "1.0.6", "@types/katex": "^0.16.7", "@types/matter-js": "0.19.7", "@types/micromatch": "4.0.9", - "@types/node": "20.14.12", + "@types/node": "22.9.0", "@types/punycode": "2.1.4", "@types/sanitize-html": "2.13.0", "@types/seedrandom": "3.0.8", "@types/throttle-debounce": "5.0.2", "@types/tinycolor2": "1.4.6", "@types/uuid": "10.0.0", - "@types/ws": "8.5.12", + "@types/ws": "8.5.13", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "@vitest/coverage-v8": "1.6.0", - "@vue/runtime-core": "3.5.10", - "acorn": "8.12.1", + "@vue/runtime-core": "3.5.12", + "acorn": "8.14.0", "cross-env": "7.0.3", - "eslint-plugin-import": "2.30.0", - "eslint-plugin-vue": "9.28.0", + "eslint-plugin-import": "2.31.0", + "eslint-plugin-vue": "9.31.0", "fast-glob": "3.3.2", "happy-dom": "10.0.3", "intersection-observer": "0.12.2", "micromatch": "4.0.8", - "msw": "2.4.9", - "msw-storybook-addon": "2.0.3", + "msw": "2.6.4", + "msw-storybook-addon": "2.0.4", "nodemon": "3.1.7", "prettier": "3.3.3", "react": "18.3.1", "react-dom": "18.3.1", "seedrandom": "3.0.5", "start-server-and-test": "2.0.8", - "storybook": "8.3.3", + "storybook": "8.4.4", "storybook-addon-misskey-theme": "github:misskey-dev/storybook-addon-misskey-theme", "vite-plugin-turbosnap": "1.0.3", "vitest": "1.6.0", "vitest-fetch-mock": "0.2.2", - "vue-component-type-helpers": "2.1.6", + "vue-component-type-helpers": "2.1.10", "vue-eslint-parser": "9.4.3", - "vue-tsc": "2.1.6" + "vue-tsc": "2.1.10" } } diff --git a/packages/frontend/src/_boot_.ts b/packages/frontend/src/_boot_.ts index 875353f8a4..5c39955c28 100644 --- a/packages/frontend/src/_boot_.ts +++ b/packages/frontend/src/_boot_.ts @@ -10,7 +10,7 @@ import '@/style.scss'; import { mainBoot } from '@/boot/main-boot.js'; import { subBoot } from '@/boot/sub-boot.js'; -const subBootPaths = ['/share', '/auth', '/miauth', '/signup-complete']; +const subBootPaths = ['/share', '/auth', '/miauth', '/oauth', '/signup-complete']; if (subBootPaths.some(i => location.pathname === i || location.pathname.startsWith(i + '/'))) { subBoot(); diff --git a/packages/frontend/src/_dev_boot_.ts b/packages/frontend/src/_dev_boot_.ts index 1601f247d7..f312765dcf 100644 --- a/packages/frontend/src/_dev_boot_.ts +++ b/packages/frontend/src/_dev_boot_.ts @@ -43,7 +43,7 @@ async function main() { const theme = localStorage.getItem('theme'); if (theme) { for (const [k, v] of Object.entries(JSON.parse(theme))) { - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); // HTMLã® theme-color é©ç”¨ if (k === 'htmlThemeColor') { diff --git a/packages/frontend/src/account.ts b/packages/frontend/src/account.ts index f0a464084f..366345b5b3 100644 --- a/packages/frontend/src/account.ts +++ b/packages/frontend/src/account.ts @@ -5,12 +5,12 @@ import { defineAsyncComponent, reactive, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { apiUrl } from '@@/js/config.js'; +import type { MenuItem, MenuButton } from '@/types/menu.js'; import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; -import type { MenuItem, MenuButton } from '@/types/menu.js'; import { del, get, set } from '@/scripts/idb-proxy.js'; -import { apiUrl } from '@@/js/config.js'; import { waiting, popup, popupMenu, success, alert } from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { unisonReload, reloadChannel } from '@/scripts/unison-reload.js'; @@ -173,7 +173,18 @@ function fetchAccount(token: string, id?: string, forceShowDialog?: boolean): Pr }); } -export function updateAccount(accountData: Partial<Account>) { +export function updateAccount(accountData: Account) { + if (!$i) return; + for (const key of Object.keys($i)) { + delete $i[key]; + } + for (const [key, value] of Object.entries(accountData)) { + $i[key] = value; + } + miLocalStorage.setItem('account', JSON.stringify($i)); +} + +export function updateAccountPartial(accountData: Partial<Account>) { if (!$i) return; for (const [key, value] of Object.entries(accountData)) { $i[key] = value; @@ -232,26 +243,6 @@ export async function openAccountMenu(opts: { }, ev: MouseEvent) { if (!$i) return; - function showSigninDialog() { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { - done: res => { - addAccount(res.id, res.i); - success(); - }, - closed: () => dispose(), - }); - } - - function createAccount() { - const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { - done: res => { - addAccount(res.id, res.i); - switchAccountWithToken(res.i); - }, - closed: () => dispose(), - }); - } - async function switchAccount(account: Misskey.entities.UserDetailed) { const storedAccounts = await getAccounts(); const found = storedAccounts.find(x => x.id === account.id); @@ -320,10 +311,22 @@ export async function openAccountMenu(opts: { text: i18n.ts.addAccount, children: [{ text: i18n.ts.existingAccount, - action: () => { showSigninDialog(); }, + action: () => { + getAccountWithSigninDialog().then(res => { + if (res != null) { + success(); + } + }); + }, }, { text: i18n.ts.createAccount, - action: () => { createAccount(); }, + action: () => { + getAccountWithSignupDialog().then(res => { + if (res != null) { + switchAccountWithToken(res.token); + } + }); + }, }], }, { type: 'link', @@ -349,6 +352,40 @@ export async function openAccountMenu(opts: { }); } +export function getAccountWithSigninDialog(): Promise<{ id: string, token: string } | null> { + return new Promise((resolve) => { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { + done: async (res: Misskey.entities.SigninFlowResponse & { finished: true }) => { + await addAccount(res.id, res.i); + resolve({ id: res.id, token: res.i }); + }, + cancelled: () => { + resolve(null); + }, + closed: () => { + dispose(); + }, + }); + }); +} + +export function getAccountWithSignupDialog(): Promise<{ id: string, token: string } | null> { + return new Promise((resolve) => { + const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { + done: async (res: Misskey.entities.SignupResponse) => { + await addAccount(res.id, res.token); + resolve({ id: res.id, token: res.token }); + }, + cancelled: () => { + resolve(null); + }, + closed: () => { + dispose(); + }, + }); + }); +} + if (_DEV_) { (window as any).$i = $i; } diff --git a/packages/frontend/src/boot/common.ts b/packages/frontend/src/boot/common.ts index af8bbf57d2..d43a2b0799 100644 --- a/packages/frontend/src/boot/common.ts +++ b/packages/frontend/src/boot/common.ts @@ -11,11 +11,11 @@ import directives from '@/directives/index.js'; import components from '@/components/index.js'; import { applyTheme } from '@/scripts/theme.js'; import { isDeviceDarkmode } from '@/scripts/is-device-darkmode.js'; -import { updateI18n } from '@/i18n.js'; +import { updateI18n, i18n } from '@/i18n.js'; import { $i, refreshAccount, login } from '@/account.js'; import { defaultStore, ColdDeviceStorage } from '@/store.js'; import { fetchInstance, instance } from '@/instance.js'; -import { deviceKind } from '@/scripts/device-kind.js'; +import { deviceKind, updateDeviceKind } from '@/scripts/device-kind.js'; import { reloadChannel } from '@/scripts/unison-reload.js'; import { getUrlWithoutLoginId } from '@/scripts/login-id.js'; import { getAccountFromId } from '@/scripts/get-account-from-id.js'; @@ -183,24 +183,22 @@ export async function common(createVue: () => App<Element>) { if (instance.defaultLightTheme != null) ColdDeviceStorage.set('lightTheme', JSON.parse(instance.defaultLightTheme)); if (instance.defaultDarkTheme != null) ColdDeviceStorage.set('darkTheme', JSON.parse(instance.defaultDarkTheme)); defaultStore.set('themeInitial', false); - } else { - if (defaultStore.state.darkMode) { - applyTheme(darkTheme.value); - } else { - applyTheme(lightTheme.value); - } } }); + watch(defaultStore.reactiveState.overridedDeviceKind, (kind) => { + updateDeviceKind(kind); + }, { immediate: true }); + watch(defaultStore.reactiveState.useBlurEffectForModal, v => { - document.documentElement.style.setProperty('--modalBgFilter', v ? 'blur(4px)' : 'none'); + document.documentElement.style.setProperty('--MI-modalBgFilter', v ? 'blur(4px)' : 'none'); }, { immediate: true }); watch(defaultStore.reactiveState.useBlurEffect, v => { if (v) { - document.documentElement.style.removeProperty('--blur'); + document.documentElement.style.removeProperty('--MI-blur'); } else { - document.documentElement.style.setProperty('--blur', 'none'); + document.documentElement.style.setProperty('--MI-blur', 'none'); } }, { immediate: true }); @@ -280,6 +278,27 @@ export async function common(createVue: () => App<Element>) { removeSplash(); + //#region Self-XSS 対ç–メッセージ + console.log( + `%c${i18n.ts._selfXssPrevention.warning}`, + 'color: #f00; background-color: #ff0; font-size: 36px; padding: 4px;', + ); + console.log( + `%c${i18n.ts._selfXssPrevention.title}`, + 'color: #f00; font-weight: 900; font-family: "Hiragino Sans W9", "Hiragino Kaku Gothic ProN", sans-serif; font-size: 24px;', + ); + console.log( + `%c${i18n.ts._selfXssPrevention.description1}`, + 'font-size: 16px; font-weight: 700;', + ); + console.log( + `%c${i18n.ts._selfXssPrevention.description2}`, + 'font-size: 16px;', + 'font-size: 20px; font-weight: 700; color: #f00;', + ); + console.log(i18n.tsx._selfXssPrevention.description3({ link: 'https://misskey-hub.net/docs/for-users/resources/self-xss/' })); + //#endregion + return { isClientUpdated, app, diff --git a/packages/frontend/src/boot/main-boot.ts b/packages/frontend/src/boot/main-boot.ts index 395d7d9ad1..eb8a4d30d2 100644 --- a/packages/frontend/src/boot/main-boot.ts +++ b/packages/frontend/src/boot/main-boot.ts @@ -4,14 +4,14 @@ */ import { createApp, defineAsyncComponent, markRaw } from 'vue'; +import { ui } from '@@/js/config.js'; import { common } from './common.js'; import type * as Misskey from 'misskey-js'; -import { ui } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { alert, confirm, popup, post, toast } from '@/os.js'; import { useStream } from '@/stream.js'; import * as sound from '@/scripts/sound.js'; -import { $i, signout, updateAccount } from '@/account.js'; +import { $i, signout, updateAccountPartial } from '@/account.js'; import { instance } from '@/instance.js'; import { ColdDeviceStorage, defaultStore } from '@/store.js'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -230,11 +230,41 @@ export async function mainBoot() { } if (!claimedAchievements.includes('justPlainLucky')) { - window.setInterval(() => { + let justPlainLuckyTimer: number | null = null; + let lastVisibilityChangedAt = Date.now(); + + function claimPlainLucky() { + if (document.visibilityState !== 'visible') { + if (justPlainLuckyTimer != null) window.clearTimeout(justPlainLuckyTimer); + return; + } + if (Math.floor(Math.random() * 20000) === 0) { claimAchievement('justPlainLucky'); + } else { + justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10); } - }, 1000 * 10); + } + + window.addEventListener('visibilitychange', () => { + const now = Date.now(); + + if (document.visibilityState === 'visible') { + // タブを高速ã§åˆ‡ã‚Šæ›¿ãˆãŸã‚‰å–得処ç†ãŒä½•度も走るã®ã‚’防ã + if ((now - lastVisibilityChangedAt) < 1000 * 10) { + justPlainLuckyTimer = window.setTimeout(claimPlainLucky, 1000 * 10); + } else { + claimPlainLucky(); + } + } else if (justPlainLuckyTimer != null) { + window.clearTimeout(justPlainLuckyTimer); + justPlainLuckyTimer = null; + } + + lastVisibilityChangedAt = now; + }, { passive: true }); + + claimPlainLucky(); } if (!claimedAchievements.includes('client30min')) { @@ -298,13 +328,13 @@ export async function mainBoot() { // è‡ªåˆ†ã®æƒ…å ±ãŒæ›´æ–°ã•れãŸã¨ã main.on('meUpdated', i => { - updateAccount(i); + updateAccountPartial(i); }); main.on('readAllNotifications', () => { setFavIconDot(false); - updateAccount({ + updateAccountPartial({ hasUnreadNotification: false, unreadNotificationsCount: 0, }); @@ -314,39 +344,39 @@ export async function mainBoot() { attemptShowNotificationDot(); const unreadNotificationsCount = ($i?.unreadNotificationsCount ?? 0) + 1; - updateAccount({ + updateAccountPartial({ hasUnreadNotification: true, unreadNotificationsCount, }); }); main.on('unreadMention', () => { - updateAccount({ hasUnreadMentions: true }); + updateAccountPartial({ hasUnreadMentions: true }); }); main.on('readAllUnreadMentions', () => { - updateAccount({ hasUnreadMentions: false }); + updateAccountPartial({ hasUnreadMentions: false }); }); main.on('unreadSpecifiedNote', () => { - updateAccount({ hasUnreadSpecifiedNotes: true }); + updateAccountPartial({ hasUnreadSpecifiedNotes: true }); }); main.on('readAllUnreadSpecifiedNotes', () => { - updateAccount({ hasUnreadSpecifiedNotes: false }); + updateAccountPartial({ hasUnreadSpecifiedNotes: false }); }); main.on('readAllAntennas', () => { - updateAccount({ hasUnreadAntenna: false }); + updateAccountPartial({ hasUnreadAntenna: false }); }); main.on('unreadAntenna', () => { - updateAccount({ hasUnreadAntenna: true }); + updateAccountPartial({ hasUnreadAntenna: true }); sound.playMisskeySfx('antenna'); }); main.on('readAllAnnouncements', () => { - updateAccount({ hasUnreadAnnouncement: false }); + updateAccountPartial({ hasUnreadAnnouncement: false }); }); // 個人宛ã¦ãŠçŸ¥ã‚‰ã›ãŒç™ºè¡Œã•れãŸã¨ã diff --git a/packages/frontend/src/components/MkAbuseReport.vue b/packages/frontend/src/components/MkAbuseReport.vue index d13eedaade..d59c5b2c57 100644 --- a/packages/frontend/src/components/MkAbuseReport.vue +++ b/packages/frontend/src/components/MkAbuseReport.vue @@ -4,141 +4,151 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> - <div class="bcekxzvu _margin _panel"> - <div class="target"> - <MkA v-user-preview="report.targetUserId" class="info" :to="`/admin/user/${report.targetUserId}`" :behavior="'window'"> - <MkAvatar class="avatar" :user="report.targetUser" indicator/> - <div class="names"> - <MkUserName class="name" :user="report.targetUser"/> - <MkAcct class="acct" :user="report.targetUser" style="display: block;"/> - </div> - </MkA> - <div class="keyvalCtn"> - <MkKeyValue> - <template #key>{{ i18n.ts.registeredDate }}</template> - <template #value>{{ dateString(report.targetUser.createdAt) }} (<MkTime :time="report.targetUser.createdAt"/>)</template> - </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.reporter }}</template> - <template #value><MkA :to="`/admin/user/${report.reporter.id}`" class="_link" :behavior="'window'">@{{ report.reporter.username }}</MkA></template> - </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts.createdAt }}</template> - <template #value><MkTime :time="report.createdAt" mode="absolute"/> (<MkTime :time="report.createdAt" mode="relative"/>)</template> - </MkKeyValue> - </div> - <hr> +<MkFolder> + <template #icon> + <i v-if="report.resolved && report.resolvedAs === 'accept'" class="ti ti-check" style="color: var(--MI_THEME-success)"></i> + <i v-else-if="report.resolved && report.resolvedAs === 'reject'" class="ti ti-x" style="color: var(--MI_THEME-error)"></i> + <i v-else-if="report.resolved" class="ti ti-slash"></i> + <i v-else class="ti ti-exclamation-circle" style="color: var(--MI_THEME-warn)"></i> + </template> + <template #label><MkAcct :user="report.targetUser"/> (by <MkAcct :user="report.reporter"/>)</template> + <template #caption>{{ report.comment }}</template> + <template #suffix><MkTime :time="report.createdAt"/></template> + <template #footer> + <div class="_buttons"> + <template v-if="!report.resolved"> + <MkButton @click="resolve('accept')"><i class="ti ti-check" style="color: var(--MI_THEME-success)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.accept }})</MkButton> + <MkButton @click="resolve('reject')"><i class="ti ti-x" style="color: var(--MI_THEME-error)"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts._abuseUserReport.reject }})</MkButton> + <MkButton @click="resolve(null)"><i class="ti ti-slash"></i> {{ i18n.ts._abuseUserReport.resolve }} ({{ i18n.ts.other }})</MkButton> + </template> + <template v-if="report.targetUser.host != null"> + <MkButton :disabled="report.forwarded" primary @click="forward"><i class="ti ti-corner-up-right"></i> {{ i18n.ts._abuseUserReport.forward }}</MkButton> + <div v-tooltip:dialog="i18n.ts._abuseUserReport.forwardDescription" class="_button _help"><i class="ti ti-help-circle"></i></div> + </template> + <button class="_button" style="margin-left: auto; width: 34px;" @click="showMenu"><i class="ti ti-dots"></i></button> </div> - <div class="detail"> - <div> + </template> + + <div class="_gaps_s"> + <MkFolder :withSpacer="false"> + <template #icon><MkAvatar :user="report.targetUser" style="width: 18px; height: 18px;"/></template> + <template #label>{{ i18n.ts.target }}: <MkAcct :user="report.targetUser"/></template> + <template #suffix>#{{ report.targetUserId.toUpperCase() }}</template> + + <div style="container-type: inline-size;"> + <RouterView :router="targetRouter"/> + </div> + </MkFolder> + + <MkFolder :defaultOpen="true"> + <template #icon><i class="ti ti-message-2"></i></template> + <template #label>{{ i18n.ts.details }}</template> + <div class="_gaps_s"> <Mfm :text="report.comment" :isBlock="true" :linkNavigationBehavior="'window'"/> </div> - <hr/> - <div v-if="report.assignee" class="assignee"> - {{ i18n.ts.moderator }}: - <MkA :to="`/admin/user/${report.assignee.id}`" class="_link" :behavior="'window'">@{{ report.assignee.username }}</MkA> + </MkFolder> + + <MkFolder :withSpacer="false"> + <template #icon><MkAvatar :user="report.reporter" style="width: 18px; height: 18px;"/></template> + <template #label>{{ i18n.ts.reporter }}: <MkAcct :user="report.reporter"/></template> + <template #suffix>#{{ report.reporterId.toUpperCase() }}</template> + + <div style="container-type: inline-size;"> + <RouterView :router="reporterRouter"/> </div> - <div class="action"> - <MkSwitch v-model="forward" c:disabled="report.targetUser.host == null || report.resolved"> - {{ i18n.ts.forwardReport }} - <template #caption>{{ i18n.ts.forwardReportIsAnonymous }}</template> - </MkSwitch> - <MkButton v-if="!report.resolved" primary @click="resolve">{{ i18n.ts.abuseMarkAsResolved }}</MkButton> + </MkFolder> + + <MkFolder :defaultOpen="false"> + <template #icon><i class="ti ti-message-2"></i></template> + <template #label>{{ i18n.ts.moderationNote }}</template> + <template #suffix>{{ moderationNote.length > 0 ? '...' : i18n.ts.none }}</template> + <div class="_gaps_s"> + <MkTextarea v-model="moderationNote" manualSave> + <template #caption>{{ i18n.ts.moderationNoteDescription }}</template> + </MkTextarea> </div> + </MkFolder> + + <div v-if="report.assignee"> + {{ i18n.ts.moderator }}: + <MkAcct :user="report.assignee"/> </div> </div> +</MkFolder> </template> <script lang="ts" setup> -import { ref } from 'vue'; +import { provide, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import MkButton from '@/components/MkButton.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { dateString } from '@/filters/date.js'; +import MkFolder from '@/components/MkFolder.vue'; +import RouterView from '@/components/global/RouterView.vue'; +import { useRouterFactory } from '@/router/supplier'; +import MkTextarea from '@/components/MkTextarea.vue'; +import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; const props = defineProps<{ - report: any; + report: Misskey.entities.AdminAbuseUserReportsResponse[number]; }>(); const emit = defineEmits<{ (ev: 'resolved', reportId: string): void; }>(); -const forward = ref(props.report.forwarded); +const routerFactory = useRouterFactory(); +const targetRouter = routerFactory(`/admin/user/${props.report.targetUserId}`); +targetRouter.init(); +const reporterRouter = routerFactory(`/admin/user/${props.report.reporterId}`); +reporterRouter.init(); + +const moderationNote = ref(props.report.moderationNote ?? ''); -function resolve() { +watch(moderationNote, async () => { + os.apiWithDialog('admin/update-abuse-user-report', { + reportId: props.report.id, + moderationNote: moderationNote.value, + }).then(() => { + }); +}); + +function resolve(resolvedAs) { os.apiWithDialog('admin/resolve-abuse-user-report', { - forward: forward.value, reportId: props.report.id, + resolvedAs, }).then(() => { emit('resolved', props.report.id); }); } -</script> -<style lang="scss" scoped> -.bcekxzvu { - display: flex; - flex-direction: column; - transition: .1s; - - > .target { - box-sizing: border-box; - text-align: left; - padding: 24px 24px 0px 24px; - - > .info { - display: flex; - box-sizing: border-box; - align-items: center; - padding: 14px; - border-radius: var(--radius-sm); - --c: rgb(255 196 0 / 15%); - background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); - background-size: 16px 16px; - - > .avatar { - width: 42px; - height: 42px; - } - - > .names { - margin-left: 0.3em; - padding: 0 8px; - flex: 1; - - white-space: pre; - overflow: hidden; - - > .name { - font-weight: bold; - } - } - } - - > .keyvalCtn { - display: inline-flex; - gap: 15px; - margin-top: 15px; - } - } - - > .detail { - display: flex; - flex-direction: column; - padding: 0px 24px 24px 24px; +function forward() { + os.apiWithDialog('admin/forward-abuse-user-report', { + reportId: props.report.id, + }).then(() => { - .assignee { - margin-bottom: 15px; - } + }); +} - .action { - display: flex; - flex-direction: column; - gap: 15px; - } - } +function showMenu(ev: MouseEvent) { + os.popupMenu([{ + icon: 'ti ti-id', + text: 'Copy ID', + action: () => { + copyToClipboard(props.report.id); + }, + }, { + icon: 'ti ti-json', + text: 'Copy JSON', + action: () => { + copyToClipboard(JSON.stringify(props.report, null, '\t')); + }, + }], ev.currentTarget ?? ev.target); } +</script> + +<style lang="scss" module> </style> diff --git a/packages/frontend/src/components/MkAccountMoved.vue b/packages/frontend/src/components/MkAccountMoved.vue index 796524fce9..0839955d9d 100644 --- a/packages/frontend/src/components/MkAccountMoved.vue +++ b/packages/frontend/src/components/MkAccountMoved.vue @@ -32,9 +32,9 @@ misskeyApi('users/show', { userId: props.movedTo }).then(u => user.value = u); .root { padding: 16px; font-size: 90%; - background: var(--infoWarnBg); - color: var(--error); - border-radius: var(--radius); + background: var(--MI_THEME-infoWarnBg); + color: var(--MI_THEME-error); + border-radius: var(--MI-radius); } .link { diff --git a/packages/frontend/src/components/MkAchievements.vue b/packages/frontend/src/components/MkAchievements.vue index ab7bafc47a..087ad51fe3 100644 --- a/packages/frontend/src/components/MkAchievements.vue +++ b/packages/frontend/src/components/MkAchievements.vue @@ -121,10 +121,10 @@ onMounted(() => { .iconFrame { position: relative; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); padding: 6px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); box-sizing: border-box; pointer-events: none; user-select: none; @@ -191,7 +191,7 @@ onMounted(() => { position: relative; width: 100%; height: 100%; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); box-shadow: 0 1px 0px #ffffff88 inset; } diff --git a/packages/frontend/src/components/MkAnalogClock.vue b/packages/frontend/src/components/MkAnalogClock.vue index 835efbd6cd..c8fa6246e0 100644 --- a/packages/frontend/src/components/MkAnalogClock.vue +++ b/packages/frontend/src/components/MkAnalogClock.vue @@ -193,12 +193,12 @@ tick(); function calcColors() { const computedStyle = getComputedStyle(document.documentElement); - const dark = tinycolor(computedStyle.getPropertyValue('--bg')).isDark(); - const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + const dark = tinycolor(computedStyle.getPropertyValue('--MI_THEME-bg')).isDark(); + const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); majorGraduationColor.value = dark ? 'rgba(255, 255, 255, 0.3)' : 'rgba(0, 0, 0, 0.3)'; //minorGraduationColor = dark ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; sHandColor.value = dark ? 'rgba(255, 255, 255, 0.5)' : 'rgba(0, 0, 0, 0.3)'; - mHandColor.value = tinycolor(computedStyle.getPropertyValue('--fg')).toHexString(); + mHandColor.value = tinycolor(computedStyle.getPropertyValue('--MI_THEME-fg')).toHexString(); hHandColor.value = accent; nowColor.value = accent; } diff --git a/packages/frontend/src/components/MkAnnouncementDialog.vue b/packages/frontend/src/components/MkAnnouncementDialog.vue index c81fea175c..6c335d71d9 100644 --- a/packages/frontend/src/components/MkAnnouncementDialog.vue +++ b/packages/frontend/src/components/MkAnnouncementDialog.vue @@ -9,9 +9,9 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.header"> <span :class="$style.icon"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </span> <span :class="$style.title">{{ announcement.title }}</span> </div> @@ -29,7 +29,7 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import MkModal from '@/components/MkModal.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; -import { $i, updateAccount } from '@/account.js'; +import { $i, updateAccountPartial } from '@/account.js'; const props = withDefaults(defineProps<{ announcement: Misskey.entities.Announcement; @@ -51,7 +51,7 @@ async function ok() { modal.value?.close(); misskeyApi('i/read-announcement', { announcementId: props.announcement.id }); - updateAccount({ + updateAccountPartial({ unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== props.announcement.id), }); } @@ -83,8 +83,8 @@ onMounted(() => { min-width: 320px; max-width: 480px; box-sizing: border-box; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); } .header { diff --git a/packages/frontend/src/components/MkAntennaEditor.vue b/packages/frontend/src/components/MkAntennaEditor.vue index cb7ee3d6ca..e622d57f1e 100644 --- a/packages/frontend/src/components/MkAntennaEditor.vue +++ b/packages/frontend/src/components/MkAntennaEditor.vue @@ -160,7 +160,7 @@ async function deleteAntenna() { function addUser() { os.selectUser({ includeSelf: true }).then(user => { users.value = users.value.trim(); - users.value += '\n@' + Misskey.acct.toString(user as any); + users.value += '\n@' + Misskey.acct.toString(user); users.value = users.value.trim(); }); } @@ -170,6 +170,6 @@ function addUser() { .actions { margin-top: 16px; padding: 24px 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/components/MkAsUi.vue b/packages/frontend/src/components/MkAsUi.vue index e2af4f034e..c28dbc7ffa 100644 --- a/packages/frontend/src/components/MkAsUi.vue +++ b/packages/frontend/src/components/MkAsUi.vue @@ -106,7 +106,7 @@ const containerStyle = computed(() => { const border = isBordered ? { borderWidth: c.borderWidth ?? '1px', - borderColor: c.borderColor ?? 'var(--divider)', + borderColor: c.borderColor ?? 'var(--MI_THEME-divider)', borderStyle: c.borderStyle ?? 'solid', } : undefined; @@ -165,7 +165,7 @@ function openPostForm() { } .postForm { - background: var(--bg); - border-radius: var(--radius-sm); + background: var(--MI_THEME-bg); + border-radius: var(--MI-radius-sm); } </style> diff --git a/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts new file mode 100644 index 0000000000..0adc44e204 --- /dev/null +++ b/packages/frontend/src/components/MkAuthConfirm.stories.impl.ts @@ -0,0 +1,7 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import MkAuthConfirm from './MkAuthConfirm.vue'; +void MkAuthConfirm; diff --git a/packages/frontend/src/components/MkAuthConfirm.vue b/packages/frontend/src/components/MkAuthConfirm.vue new file mode 100644 index 0000000000..f78d2d38f0 --- /dev/null +++ b/packages/frontend/src/components/MkAuthConfirm.vue @@ -0,0 +1,450 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.wrapper"> + <Transition + mode="out-in" + :enterActiveClass="$style.transition_enterActive" + :leaveActiveClass="$style.transition_leaveActive" + :enterFromClass="$style.transition_enterFrom" + :leaveToClass="$style.transition_leaveTo" + + :inert="_waiting" + > + <div v-if="phase === 'accountSelect'" key="accountSelect" :class="$style.root" class="_gaps"> + <div :class="$style.header" class="_gaps_s"> + <div :class="$style.iconFallback"> + <i class="ti ti-user"></i> + </div> + <div :class="$style.headerText">{{ i18n.ts.pleaseSelectAccount }}</div> + </div> + <div> + <div :class="$style.accountSelectorLabel">{{ i18n.ts.selectAccount }}</div> + <div :class="$style.accountSelectorList"> + <template v-for="[id, user] in users"> + <input :id="'account-' + id" v-model="selectedUser" type="radio" name="accountSelector" :value="id" :class="$style.accountSelectorRadio"/> + <label :for="'account-' + id" :class="$style.accountSelectorItem"> + <MkAvatar :user="user" :class="$style.accountSelectorAvatar"/> + <div :class="$style.accountSelectorBody"> + <MkUserName :user="user" :class="$style.accountSelectorName"/> + <MkAcct :user="user" :class="$style.accountSelectorAcct"/> + </div> + </label> + </template> + <button class="_button" :class="[$style.accountSelectorItem, $style.accountSelectorAddAccountRoot]" @click="clickAddAccount"> + <div :class="[$style.accountSelectorAvatar, $style.accountSelectorAddAccountAvatar]"> + <i class="ti ti-user-plus"></i> + </div> + <div :class="[$style.accountSelectorBody, $style.accountSelectorName]">{{ i18n.ts.addAccount }}</div> + </button> + </div> + </div> + <div class="_buttonsCenter"> + <MkButton rounded gradate :disabled="selectedUser === null" @click="clickChooseAccount">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </div> + </div> + <div v-else-if="phase === 'consent'" key="consent" :class="$style.root" class="_gaps"> + <div :class="$style.header" class="_gaps_s"> + <img v-if="icon" :class="$style.icon" :src="getProxiedImageUrl(icon, 'preview')"/> + <div v-else :class="$style.iconFallback"> + <i class="ti ti-apps"></i> + </div> + <div :class="$style.headerText">{{ name ? i18n.tsx._auth.shareAccess({ name }) : i18n.ts._auth.shareAccessAsk }}</div> + </div> + <div v-if="permissions && permissions.length > 0" class="_gaps_s" :class="$style.permissionRoot"> + <div>{{ name ? i18n.tsx._auth.permission({ name }) : i18n.ts._auth.permissionAsk }}</div> + <div :class="$style.permissionListWrapper"> + <ul :class="$style.permissionList"> + <li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li> + </ul> + </div> + </div> + <slot name="consentAdditionalInfo"></slot> + <div> + <div :class="$style.accountSelectorLabel"> + {{ i18n.ts._auth.scopeUser }} <button class="_textButton" @click="clickBackToAccountSelect">{{ i18n.ts.switchAccount }}</button> + </div> + <div :class="$style.accountSelectorList"> + <div :class="[$style.accountSelectorItem, $style.static]"> + <MkAvatar :user="users.get(selectedUser!)!" :class="$style.accountSelectorAvatar"/> + <div :class="$style.accountSelectorBody"> + <MkUserName :user="users.get(selectedUser!)!" :class="$style.accountSelectorName"/> + <MkAcct :user="users.get(selectedUser!)!" :class="$style.accountSelectorAcct"/> + </div> + </div> + </div> + </div> + <div class="_buttonsCenter"> + <MkButton rounded @click="clickCancel">{{ i18n.ts.reject }}</MkButton> + <MkButton rounded gradate @click="clickAccept">{{ i18n.ts.accept }}</MkButton> + </div> + </div> + <div v-else-if="phase === 'success'" key="success" :class="$style.root" class="_gaps_s"> + <div :class="$style.header" class="_gaps_s"> + <div :class="$style.iconFallback"> + <i class="ti ti-check"></i> + </div> + <div :class="$style.headerText">{{ i18n.ts._auth.accepted }}</div> + <div :class="$style.headerTextSub">{{ i18n.ts._auth.pleaseGoBack }}</div> + </div> + </div> + <div v-else-if="phase === 'denied'" key="denied" :class="$style.root" class="_gaps_s"> + <div :class="$style.header" class="_gaps_s"> + <div :class="$style.iconFallback"> + <i class="ti ti-x"></i> + </div> + <div :class="$style.headerText">{{ i18n.ts._auth.denied }}</div> + </div> + </div> + <div v-else-if="phase === 'failed'" key="failed" :class="$style.root" class="_gaps_s"> + <div :class="$style.header" class="_gaps_s"> + <div :class="$style.iconFallback"> + <i class="ti ti-x"></i> + </div> + <div :class="$style.headerText">{{ i18n.ts.somethingHappened }}</div> + </div> + </div> + </Transition> + <div v-if="_waiting" :class="$style.waitingRoot"> + <MkLoading/> + </div> +</div> +</template> + +<script setup lang="ts"> +import { ref, computed } from 'vue'; +import * as Misskey from 'misskey-js'; + +import MkButton from '@/components/MkButton.vue'; + +import { $i, getAccounts, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; +import { getProxiedImageUrl } from '@/scripts/media-proxy.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; + +const props = defineProps<{ + name?: string; + icon?: string; + permissions?: (typeof Misskey.permissions[number])[]; + manualWaiting?: boolean; + waitOnDeny?: boolean; +}>(); + +const emit = defineEmits<{ + (ev: 'accept', token: string): void; + (ev: 'deny', token: string): void; +}>(); + +const waiting = ref(true); +const _waiting = computed(() => waiting.value || props.manualWaiting); +const phase = ref<'accountSelect' | 'consent' | 'success' | 'denied' | 'failed'>('accountSelect'); + +const selectedUser = ref<string | null>(null); + +const users = ref(new Map<string, Misskey.entities.UserDetailed & { token: string; }>()); + +async function init() { + waiting.value = true; + + users.value.clear(); + + if ($i) { + users.value.set($i.id, $i); + } + + const accounts = await getAccounts(); + + const accountIdsToFetch = accounts.map(a => a.id).filter(id => !users.value.has(id)); + + if (accountIdsToFetch.length > 0) { + const usersRes = await misskeyApi('users/show', { + userIds: accountIdsToFetch, + }); + + for (const user of usersRes) { + if (users.value.has(user.id)) continue; + + users.value.set(user.id, { + ...user, + token: accounts.find(a => a.id === user.id)!.token, + }); + } + } + + waiting.value = false; +} + +init(); + +function clickAddAccount(ev: MouseEvent) { + selectedUser.value = null; + + os.popupMenu([{ + text: i18n.ts.existingAccount, + action: () => { + getAccountWithSigninDialog().then(async (res) => { + if (res != null) { + os.success(); + await init(); + if (users.value.has(res.id)) { + selectedUser.value = res.id; + } + } + }); + }, + }, { + text: i18n.ts.createAccount, + action: () => { + getAccountWithSignupDialog().then(async (res) => { + if (res != null) { + os.success(); + await init(); + if (users.value.has(res.id)) { + selectedUser.value = res.id; + } + } + }); + }, + }], ev.currentTarget ?? ev.target); +} + +function clickChooseAccount() { + if (selectedUser.value === null) return; + + phase.value = 'consent'; +} + +function clickBackToAccountSelect() { + selectedUser.value = null; + phase.value = 'accountSelect'; +} + +function clickCancel() { + if (selectedUser.value === null) return; + + const user = users.value.get(selectedUser.value)!; + + const token = user.token; + + if (props.waitOnDeny) { + waiting.value = true; + } + emit('deny', token); +} + +async function clickAccept() { + if (selectedUser.value === null) return; + + const user = users.value.get(selectedUser.value)!; + + const token = user.token; + + waiting.value = true; + emit('accept', token); +} + +function showUI(state: 'success' | 'denied' | 'failed') { + phase.value = state; + waiting.value = false; +} + +defineExpose({ + showUI, +}); +</script> + +<style lang="scss" module> +.transition_enterActive, +.transition_leaveActive { + transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1); +} +.transition_enterFrom { + opacity: 0; + transform: translateX(50px); +} +.transition_leaveTo { + opacity: 0; + transform: translateX(-50px); +} + +.wrapper { + overflow-x: hidden; + overflow-x: clip; + + position: relative; + width: 100%; + height: 100%; +} + +.waitingRoot { + position: absolute; + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: color-mix(in srgb, var(--MI_THEME-panel), transparent 50%); + display: flex; + justify-content: center; + align-items: center; + z-index: 1; + cursor: wait; +} + +.root { + position: relative; + box-sizing: border-box; + width: 100%; + padding: 48px 24px; +} + +.header { + margin: 0 auto; + max-width: 320px; +} + +.icon, +.iconFallback { + display: block; + margin: 0 auto; + width: 54px; + height: 54px; +} + +.icon { + border-radius: 50%; + border: 1px solid var(--MI_THEME-divider); + background-color: #fff; + object-fit: contain; +} + +.iconFallback { + border-radius: 50%; + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + text-align: center; + line-height: 54px; + font-size: 18px; +} + +.headerText, +.headerTextSub { + text-align: center; + word-break: normal; + word-break: auto-phrase; +} + +.headerText { + font-size: 16px; + font-weight: 700; +} + +.permissionRoot { + padding: 16px; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-bg); +} + +.permissionListWrapper { + max-height: 350px; + overflow-y: auto; + padding: 12px; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); +} + +.permissionList { + margin: 0 0 0 1.5em; + padding: 0; + font-size: 90%; +} + +.accountSelectorLabel { + font-size: 0.85em; + opacity: 0.7; + margin-bottom: 8px; +} + +.accountSelectorList { + border-radius: var(--MI-radius); + border: 1px solid var(--MI_THEME-divider); + overflow: hidden; + overflow: clip; +} + +.accountSelectorRadio { + position: absolute; + clip: rect(0, 0, 0, 0); + pointer-events: none; + + &:focus-visible + .accountSelectorItem { + outline: 2px solid var(--MI_THEME-accent); + outline-offset: -4px; + } + + &:checked:focus-visible + .accountSelectorItem { + outline-color: #fff; + } + + &:checked + .accountSelectorItem { + background: var(--MI_THEME-accent); + color: #fff; + } +} + +.accountSelectorItem { + display: flex; + align-items: center; + padding: 8px; + font-size: 14px; + -webkit-tap-highlight-color: transparent; + cursor: pointer; + + &:hover { + background: var(--MI_THEME-buttonHoverBg); + } + + &.static { + cursor: unset; + + &:hover { + background: none; + } + } +} + +.accountSelectorAddAccountRoot { + width: 100%; +} + +.accountSelectorBody { + padding: 0 8px; + min-width: 0; +} + +.accountSelectorAvatar { + width: 45px; + height: 45px; +} + +.accountSelectorAddAccountAvatar { + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + font-size: 16px; + line-height: 45px; + text-align: center; + border-radius: 50%; +} + +.accountSelectorName { + display: block; + font-weight: bold; +} + +.accountSelectorAcct { + opacity: 0.5; +} +</style> diff --git a/packages/frontend/src/components/MkAutocomplete.vue b/packages/frontend/src/components/MkAutocomplete.vue index de5207f350..a0cd066c06 100644 --- a/packages/frontend/src/components/MkAutocomplete.vue +++ b/packages/frontend/src/components/MkAutocomplete.vue @@ -407,16 +407,16 @@ onBeforeUnmount(() => { text-overflow: ellipsis; &:hover { - background: var(--X3); + background: var(--MI_THEME-X3); } &[data-selected='true'] { - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff !important; } &:active { - background: var(--accentDarken); + background: var(--MI_THEME-accentDarken); color: #fff !important; } } @@ -427,7 +427,7 @@ onBeforeUnmount(() => { max-width: 28px; max-height: 28px; margin: 0 8px 0 0; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .userName { diff --git a/packages/frontend/src/components/MkButton.vue b/packages/frontend/src/components/MkButton.vue index e30f74460d..a6e5651d63 100644 --- a/packages/frontend/src/components/MkButton.vue +++ b/packages/frontend/src/components/MkButton.vue @@ -129,8 +129,8 @@ function onMousedown(evt: MouseEvent): void { font-size: 95%; box-shadow: none; text-decoration: none; - background: var(--buttonBg); - border-radius: var(--radius-xs); + background: var(--MI_THEME-buttonBg); + border-radius: var(--MI-radius-xs); overflow: clip; box-sizing: border-box; transition: background 0.1s ease; @@ -140,11 +140,11 @@ function onMousedown(evt: MouseEvent): void { } &:not(:disabled):hover { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &:not(:disabled):active { - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &.small { @@ -162,20 +162,20 @@ function onMousedown(evt: MouseEvent): void { } &.rounded { - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } &.primary { font-weight: bold; - color: var(--fgOnAccent) !important; - background: var(--accent); + color: var(--MI_THEME-fgOnAccent) !important; + background: var(--MI_THEME-accent); &:not(:disabled):hover { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } &:not(:disabled):active { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } } @@ -216,15 +216,15 @@ function onMousedown(evt: MouseEvent): void { &.gradate { font-weight: bold; - color: var(--fgOnAccent) !important; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent) !important; + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } &:not(:disabled):active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } @@ -272,7 +272,7 @@ function onMousedown(evt: MouseEvent): void { left: 0; width: 100%; height: 100%; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; pointer-events: none; } @@ -281,7 +281,7 @@ function onMousedown(evt: MouseEvent): void { position: absolute; width: 2px; height: 2px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); background: rgba(0, 0, 0, 0.1); opacity: 1; transform: scale(1); diff --git a/packages/frontend/src/components/MkCaptcha.vue b/packages/frontend/src/components/MkCaptcha.vue index ab00ea9930..e9493edbd1 100644 --- a/packages/frontend/src/components/MkCaptcha.vue +++ b/packages/frontend/src/components/MkCaptcha.vue @@ -10,6 +10,17 @@ SPDX-License-Identifier: AGPL-3.0-only <div id="mcaptcha__widget-container" class="m-captcha-style"></div> <div ref="captchaEl"></div> </div> + <div v-if="props.provider == 'testcaptcha'" style="background: #eee; border: solid 1px #888; padding: 8px; color: #000; max-width: 320px; display: flex; gap: 10px; align-items: center; box-shadow: 2px 2px 6px #0004; border-radius: 4px;"> + <img src="/client-assets/testcaptcha.png" style="width: 60px; height: 60px; "/> + <div v-if="testcaptchaPassed"> + <div style="color: green;">Test captcha passed!</div> + </div> + <div v-else> + <div style="font-size: 13px; margin-bottom: 4px;">Type "ai-chan-kawaii" to pass captcha</div> + <input v-model="testcaptchaInput" data-cy-testcaptcha-input/> + <button type="button" data-cy-testcaptcha-submit @click="testcaptchaSubmit">Submit</button> + </div> + </div> <div v-else ref="captchaEl"></div> </div> </template> @@ -32,7 +43,7 @@ export type Captcha = { }): void; }; -export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'fc'; +export type CaptchaProvider = 'hcaptcha' | 'recaptcha' | 'turnstile' | 'mcaptcha' | 'fc' | 'testcaptcha'; type CaptchaContainer = { readonly [_ in CaptchaProvider]?: Captcha; @@ -57,6 +68,9 @@ const available = ref(false); const captchaEl = shallowRef<HTMLDivElement | undefined>(); +const testcaptchaInput = ref(''); +const testcaptchaPassed = ref(false); + const variable = computed(() => { switch (props.provider) { case 'hcaptcha': return 'hcaptcha'; @@ -64,6 +78,7 @@ const variable = computed(() => { case 'turnstile': return 'turnstile'; case 'mcaptcha': return 'mcaptcha'; case 'fc': return 'friendlyChallenge'; + case 'testcaptcha': return 'testcaptcha'; } }); @@ -76,6 +91,7 @@ const src = computed(() => { case 'turnstile': return 'https://challenges.cloudflare.com/turnstile/v0/api.js?render=explicit'; case 'fc': return 'https://cdn.jsdelivr.net/npm/friendly-challenge@0.9.18/widget.min.js'; case 'mcaptcha': return null; + case 'testcaptcha': return null; } }); @@ -83,7 +99,7 @@ const scriptId = computed(() => `script-${props.provider}`); const captcha = computed<Captcha>(() => window[variable.value] || {} as unknown as Captcha); -if (loaded || props.provider === 'mcaptcha') { +if (loaded || props.provider === 'mcaptcha' || props.provider === 'testcaptcha') { available.value = true; } else if (src.value !== null) { (document.getElementById(scriptId.value) ?? document.head.appendChild(Object.assign(document.createElement('script'), { @@ -96,6 +112,8 @@ if (loaded || props.provider === 'mcaptcha') { function reset() { if (captcha.value.reset) captcha.value.reset(); + testcaptchaPassed.value = false; + testcaptchaInput.value = ''; } async function requestRender() { @@ -104,8 +122,8 @@ async function requestRender() { sitekey: props.sitekey, theme: defaultStore.state.darkMode ? 'dark' : 'light', callback: callback, - 'expired-callback': callback, - 'error-callback': callback, + 'expired-callback': () => callback(undefined), + 'error-callback': () => callback(undefined), }); } else if (props.provider === 'mcaptcha' && props.instanceUrl && props.sitekey) { const { default: Widget } = await import('@mcaptcha/vanilla-glue'); @@ -140,6 +158,12 @@ function onReceivedMessage(message: MessageEvent) { } } +function testcaptchaSubmit() { + testcaptchaPassed.value = testcaptchaInput.value === 'ai-chan-kawaii'; + callback(testcaptchaPassed.value ? 'testcaptcha-passed' : undefined); + if (!testcaptchaPassed.value) testcaptchaInput.value = ''; +} + onMounted(() => { if (available.value) { window.addEventListener('message', onReceivedMessage); diff --git a/packages/frontend/src/components/MkChannelFollowButton.vue b/packages/frontend/src/components/MkChannelFollowButton.vue index 6dace43fde..7aa916134f 100644 --- a/packages/frontend/src/components/MkChannelFollowButton.vue +++ b/packages/frontend/src/components/MkChannelFollowButton.vue @@ -68,13 +68,13 @@ async function onClick() { position: relative; display: inline-block; font-weight: bold; - color: var(--accent); + color: var(--MI_THEME-accent); background: transparent; - border: solid 1px var(--accent); + border: solid 1px var(--MI_THEME-accent); padding: 0; height: 31px; font-size: 16px; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); background: #fff; &.full { @@ -99,17 +99,17 @@ async function onClick() { } &.active { - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); - border-color: var(--accentLighten); + background: var(--MI_THEME-accentLighten); + border-color: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); - border-color: var(--accentDarken); + background: var(--MI_THEME-accentDarken); + border-color: var(--MI_THEME-accentDarken); } } diff --git a/packages/frontend/src/components/MkChannelPreview.vue b/packages/frontend/src/components/MkChannelPreview.vue index 2f7ec34d44..e036fec528 100644 --- a/packages/frontend/src/components/MkChannelPreview.vue +++ b/packages/frontend/src/components/MkChannelPreview.vue @@ -47,11 +47,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref, watch } from 'vue'; +import * as Misskey from 'misskey-js'; import { i18n } from '@/i18n.js'; import { miLocalStorage } from '@/local-storage.js'; const props = defineProps<{ - channel: Record<string, any>; + channel: Misskey.entities.Channel; }>(); const getLastReadedAt = (): number | null => { @@ -100,7 +101,7 @@ const bannerStyle = computed(() => { height: 100%; border-radius: inherit; pointer-events: none; - box-shadow: inset 0 0 0 2px var(--focus); + box-shadow: inset 0 0 0 2px var(--MI_THEME-focus); } } @@ -117,7 +118,7 @@ const bannerStyle = computed(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); } > .name { @@ -138,7 +139,7 @@ const bannerStyle = computed(() => { padding: 8px 12px; font-size: 80%; background: rgba(0, 0, 0, 0.7); - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); color: #fff; } @@ -148,8 +149,8 @@ const bannerStyle = computed(() => { bottom: 16px; left: 16px; background: rgba(0, 0, 0, 0.7); - color: var(--warn); - border-radius: var(--radius-sm); + color: var(--MI_THEME-warn); + border-radius: var(--MI-radius-sm); font-weight: bold; font-size: 1em; padding: 4px 7px; @@ -167,7 +168,7 @@ const bannerStyle = computed(() => { > footer { padding: 12px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); > span { opacity: 0.7; @@ -213,9 +214,9 @@ const bannerStyle = computed(() => { top: 0; right: 0; transform: translate(25%, -25%); - background-color: var(--accent); - border: solid var(--bg) 4px; - border-radius: var(--radius-full); + background-color: var(--MI_THEME-accent); + border: solid var(--MI_THEME-bg) 4px; + border-radius: var(--MI-radius-full); width: 1.5rem; height: 1.5rem; aspect-ratio: 1 / 1; diff --git a/packages/frontend/src/components/MkChart.vue b/packages/frontend/src/components/MkChart.vue index 57d325b11a..d05f4921f6 100644 --- a/packages/frontend/src/components/MkChart.vue +++ b/packages/frontend/src/components/MkChart.vue @@ -863,8 +863,8 @@ onMounted(() => { left: 0; width: 100%; height: 100%; - -webkit-backdrop-filter: var(--blur, blur(12px)); - backdrop-filter: var(--blur, blur(12px)); + -webkit-backdrop-filter: var(--MI-blur, blur(12px)); + backdrop-filter: var(--MI-blur, blur(12px)); display: flex; justify-content: center; align-items: center; diff --git a/packages/frontend/src/components/MkChartLegend.vue b/packages/frontend/src/components/MkChartLegend.vue index 240c9c919e..e28d6ad6ba 100644 --- a/packages/frontend/src/components/MkChartLegend.vue +++ b/packages/frontend/src/components/MkChartLegend.vue @@ -53,11 +53,11 @@ defineExpose({ > .item { font-size: 85%; padding: 4px 12px 4px 8px; - border: solid 1px var(--divider); - border-radius: var(--radius-ellipse); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-ellipse); &:hover { - border-color: var(--inputBorderHover); + border-color: var(--MI_THEME-inputBorderHover); } &.disabled { @@ -69,7 +69,7 @@ defineExpose({ display: inline-block; width: 12px; height: 12px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); vertical-align: -10%; } } diff --git a/packages/frontend/src/components/MkClipPreview.vue b/packages/frontend/src/components/MkClipPreview.vue index dd550733cb..5b09ec90dd 100644 --- a/packages/frontend/src/components/MkClipPreview.vue +++ b/packages/frontend/src/components/MkClipPreview.vue @@ -49,13 +49,13 @@ const remaining = computed(() => { outline: none; .root { - box-shadow: inset 0 0 0 2px var(--focus); + box-shadow: inset 0 0 0 2px var(--MI_THEME-focus); } } &:hover { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -65,7 +65,7 @@ const remaining = computed(() => { .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .description { diff --git a/packages/frontend/src/components/MkCode.core.vue b/packages/frontend/src/components/MkCode.core.vue index 9e54420034..e253b1b55f 100644 --- a/packages/frontend/src/components/MkCode.core.vue +++ b/packages/frontend/src/components/MkCode.core.vue @@ -95,8 +95,8 @@ watch(() => props.lang, (to) => { padding: 1em; margin: .5em 0; overflow: auto; - border-radius: var(--radius-sm); - border: 1px solid var(--divider); + border-radius: var(--MI-radius-sm); + border: 1px solid var(--MI_THEME-divider); font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; color: var(--shiki-fallback); @@ -140,7 +140,7 @@ watch(() => props.lang, (to) => { & :global(.shiki) { padding: 12px; margin: 0; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); border: none; min-height: 130px; pointer-events: none; diff --git a/packages/frontend/src/components/MkCode.vue b/packages/frontend/src/components/MkCode.vue index 05cde89dd9..a46b220101 100644 --- a/packages/frontend/src/components/MkCode.vue +++ b/packages/frontend/src/components/MkCode.vue @@ -71,11 +71,11 @@ function copy() { .codeBlockFallbackRoot { display: block; overflow-wrap: anywhere; - background: var(--bg); + background: var(--MI_THEME-bg); padding: 1em; margin: .5em 0; overflow: auto; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .codeBlockFallbackCode { @@ -91,11 +91,11 @@ function copy() { cursor: pointer; box-sizing: border-box; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); padding: 24px; margin-top: 4px; - color: var(--fg); - background: var(--bg); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-bg); } .codePlaceholderContainer { diff --git a/packages/frontend/src/components/MkCodeEditor.vue b/packages/frontend/src/components/MkCodeEditor.vue index b233189ab0..49b083815a 100644 --- a/packages/frontend/src/components/MkCodeEditor.vue +++ b/packages/frontend/src/components/MkCodeEditor.vue @@ -140,7 +140,7 @@ watch(v, newValue => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -158,20 +158,20 @@ watch(v, newValue => { overflow-y: hidden; box-sizing: border-box; margin: 0; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); padding: 0; - color: var(--fg); - border: solid 1px var(--panel); + color: var(--MI_THEME-fg); + border: solid 1px var(--MI_THEME-panel); transition: border-color 0.1s ease-out; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } .focused.codeEditorRoot { - border-color: var(--accent) !important; - border-radius: var(--radius-sm); + border-color: var(--MI_THEME-accent) !important; + border-radius: var(--MI-radius-sm); } .codeEditorScroller { @@ -196,10 +196,10 @@ watch(v, newValue => { resize: none; text-align: left; color: transparent; - caret-color: var(--fg); + caret-color: var(--MI_THEME-fg); background-color: transparent; border: 0; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); box-sizing: border-box; outline: 0; min-width: calc(100% - 24px); @@ -213,6 +213,6 @@ watch(v, newValue => { } .textarea::selection { - color: var(--bg); + color: var(--MI_THEME-bg); } </style> diff --git a/packages/frontend/src/components/MkCodeInline.vue b/packages/frontend/src/components/MkCodeInline.vue index 6add80d1bc..04b6e54108 100644 --- a/packages/frontend/src/components/MkCodeInline.vue +++ b/packages/frontend/src/components/MkCodeInline.vue @@ -18,7 +18,7 @@ const props = defineProps<{ display: inline-block; font-family: Consolas, Monaco, Andale Mono, Ubuntu Mono, monospace; overflow-wrap: anywhere; - background: var(--bg); + background: var(--MI_THEME-bg); padding: .1em; border-radius: .3em; } diff --git a/packages/frontend/src/components/MkColorInput.vue b/packages/frontend/src/components/MkColorInput.vue index 99aa46d561..babf356942 100644 --- a/packages/frontend/src/components/MkColorInput.vue +++ b/packages/frontend/src/components/MkColorInput.vue @@ -60,7 +60,7 @@ const onInput = () => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -72,8 +72,8 @@ const onInput = () => { &.focused { > .inputCore { - border-color: var(--accent) !important; - //box-shadow: 0 0 0 4px var(--focus); + border-color: var(--MI_THEME-accent) !important; + //box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } @@ -98,17 +98,17 @@ const onInput = () => { font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); outline: none; box-shadow: none; box-sizing: border-box; transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } </style> diff --git a/packages/frontend/src/components/MkContainer.vue b/packages/frontend/src/components/MkContainer.vue index 5f71e289b8..f4d20c7d8c 100644 --- a/packages/frontend/src/components/MkContainer.vue +++ b/packages/frontend/src/components/MkContainer.vue @@ -64,26 +64,30 @@ const showBody = ref(props.expanded); const ignoreOmit = ref(false); const omitted = ref(false); -function enter(el) { +function enter(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; - el.style.height = 0; + el.style.height = '0'; el.offsetHeight; // reflow - el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px'; + el.style.height = `${Math.min(elementHeight, props.maxHeight ?? Infinity)}px`; } -function afterEnter(el) { - el.style.height = null; +function afterEnter(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } -function leave(el) { +function leave(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; - el.style.height = elementHeight + 'px'; + el.style.height = `${elementHeight}px`; el.offsetHeight; // reflow - el.style.height = 0; + el.style.height = '0'; } -function afterLeave(el) { - el.style.height = null; +function afterLeave(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } const calcOmit = () => { @@ -138,7 +142,7 @@ onUnmounted(() => { position: relative; overflow: clip; contain: content; - background: color-mix(in srgb, var(--panel) 65%, transparent); + background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); &.naked { background: transparent !important; box-shadow: none !important; @@ -165,13 +169,14 @@ onUnmounted(() => { .header { position: sticky; - top: var(--stickyTop, 0px); + top: var(--MI-stickyTop, 0px); left: 0; - color: var(--panelHeaderFg); - border-bottom: solid 0.5px var(--panelHeaderDivider); + color: var(--MI_THEME-panelHeaderFg); + background: var(--MI_THEME-panelHeaderBg); + border-bottom: solid 0.5px var(--MI_THEME-panelHeaderDivider); z-index: 2; line-height: 1.4em; - background: color-mix(in srgb, var(--panelHeaderBg) 35%, transparent); + background: color-mix(in srgb, var(--MI_THEME-panelHeaderBg) 35%, transparent); } .title { @@ -201,7 +206,7 @@ onUnmounted(() => { } .content { - --stickyTop: 0px; + --MI-stickyTop: 0px; &.omitted { position: relative; @@ -216,20 +221,20 @@ onUnmounted(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } diff --git a/packages/frontend/src/components/MkCropperDialog.vue b/packages/frontend/src/components/MkCropperDialog.vue index 2e1e92cbdf..0186cfc2c0 100644 --- a/packages/frontend/src/components/MkCropperDialog.vue +++ b/packages/frontend/src/components/MkCropperDialog.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :withOkButton="true" @close="cancel()" @ok="ok()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header>{{ i18n.ts.cropImage }}</template> <template #default="{ width, height }"> @@ -125,7 +125,7 @@ onMounted(() => { const computedStyle = getComputedStyle(document.documentElement); const selection = cropper.getCropperSelection()!; - selection.themeColor = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + selection.themeColor = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); selection.aspectRatio = props.aspectRatio; selection.initialAspectRatio = props.aspectRatio; selection.outlined = true; @@ -170,8 +170,8 @@ onMounted(() => { display: flex; align-items: center; justify-content: center; - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); + -webkit-backdrop-filter: var(--MI-blur, blur(10px)); + backdrop-filter: var(--MI-blur, blur(10px)); background: rgba(0, 0, 0, 0.5); } diff --git a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue index c7f1288729..ecbee864dc 100644 --- a/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue +++ b/packages/frontend/src/components/MkCustomEmojiDetailedDialog.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkModalWindow ref="dialogEl" @close="cancel()" @closed="$emit('closed')"> +<MkModalWindow ref="dialogEl" @close="cancel()" @closed="emit('closed')"> <template #header>:{{ emoji.name }}:</template> <template #default> <MkSpacer> @@ -85,8 +85,8 @@ function cancel() { .emojiImgWrapper { max-width: 100%; height: 40cqh; - background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--X5) 8px, var(--X5) 14px); - border-radius: var(--radius); + background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-X5) 8px, var(--MI_THEME-X5) 14px); + border-radius: var(--MI-radius); margin: auto; overflow-y: hidden; } @@ -101,8 +101,8 @@ function cancel() { display: inline-block; word-break: break-all; padding: 3px 10px; - background-color: var(--X5); - border: solid 1px var(--divider); - border-radius: var(--radius); + background-color: var(--MI_THEME-X5); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius); } </style> diff --git a/packages/frontend/src/components/MkDateSeparatedList.vue b/packages/frontend/src/components/MkDateSeparatedList.vue index 98bf5191f7..4b9666c55c 100644 --- a/packages/frontend/src/components/MkDateSeparatedList.vue +++ b/packages/frontend/src/components/MkDateSeparatedList.vue @@ -9,6 +9,7 @@ import MkAd from '@/components/global/MkAd.vue'; import { isDebuggerEnabled, stackTraceInstances } from '@/debug.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; +import { instance } from '@/instance.js'; import { defaultStore } from '@/store.js'; import { MisskeyEntity } from '@/types/date-separated-list.js'; @@ -99,11 +100,13 @@ export default defineComponent({ return [el, separator]; } else { - if (props.ad && item._shouldInsertAd_) { - return [h(MkAd, { + if (props.ad && instance.ads.length > 0 && item._shouldInsertAd_) { + return [h('div', { key: item.id + ':ad', + class: $style['ad-wrapper'], + }, [h(MkAd, { prefer: ['horizontal', 'horizontal-big'], - }), el]; + })]), el]; } else { return el; } @@ -125,14 +128,14 @@ export default defineComponent({ return children; }; - function onBeforeLeave(element: Element) { - const el = element as HTMLElement; + function onBeforeLeave(el: Element) { + if (!(el instanceof HTMLElement)) return; el.style.top = `${el.offsetTop}px`; el.style.left = `${el.offsetLeft}px`; } - function onLeaveCancelled(element: Element) { - const el = element as HTMLElement; + function onLeaveCancelled(el: Element) { + if (!(el instanceof HTMLElement)) return; el.style.top = ''; el.style.left = ''; } @@ -182,12 +185,12 @@ export default defineComponent({ } &:not(.date-separated-list-nogap) > *:not(:last-child) { - margin-bottom: var(--margin); + margin-bottom: var(--MI-margin); } } .date-separated-list-nogap { - border-radius: var(--radius); + border-radius: var(--MI-radius); > * { margin: 0 !important; @@ -196,7 +199,7 @@ export default defineComponent({ box-shadow: none; &:not(:last-child) { - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); } } } @@ -237,7 +240,7 @@ export default defineComponent({ line-height: 32px; text-align: center; font-size: 12px; - color: var(--dateLabelFg); + color: var(--MI_THEME-dateLabelFg); } .date-1 { @@ -255,5 +258,11 @@ export default defineComponent({ .date-2-icon { margin-left: 8px; } + +.ad-wrapper { + padding: 8px; + background-size: auto auto; + background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--MI_THEME-bg) 8px, var(--MI_THEME-bg) 14px); +} </style> diff --git a/packages/frontend/src/components/MkDialog.vue b/packages/frontend/src/components/MkDialog.vue index 7dc381b662..9a59a9aac7 100644 --- a/packages/frontend/src/components/MkDialog.vue +++ b/packages/frontend/src/components/MkDialog.vue @@ -45,7 +45,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> </MkSelect> <div v-if="(showOkButton || showCancelButton) && !actions" :class="$style.buttons"> - <MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> + <MkButton v-if="showOkButton" data-cy-modal-dialog-ok inline primary rounded :autofocus="!input && !select" :disabled="okButtonDisabledReason != null" @click="ok">{{ okText ?? ((showCancelButton || input || select) ? i18n.ts.ok : i18n.ts.gotIt) }}</MkButton> <MkButton v-if="showCancelButton || input || select" data-cy-modal-dialog-cancel inline rounded @click="cancel">{{ cancelText ?? i18n.ts.cancel }}</MkButton> </div> <div v-if="actions" :class="$style.buttons"> @@ -98,7 +98,7 @@ const props = withDefaults(defineProps<{ text: string; primary?: boolean, danger?: boolean, - callback: (...args: any[]) => void; + callback: (...args: unknown[]) => void; }[]; showOkButton?: boolean; showCancelButton?: boolean; @@ -185,8 +185,8 @@ function onInputKeydown(evt: KeyboardEvent) { max-width: 480px; box-sizing: border-box; text-align: center; - background: var(--panel); - border-radius: var(--radius-md); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-md); } .icon { @@ -207,15 +207,15 @@ function onInputKeydown(evt: KeyboardEvent) { } .type_success { - color: var(--success); + color: var(--MI_THEME-success); } .type_error { - color: var(--error); + color: var(--MI_THEME-error); } .type_warning { - color: var(--warn); + color: var(--MI_THEME-warn); } .title { diff --git a/packages/frontend/src/components/MkDivider.vue b/packages/frontend/src/components/MkDivider.vue index e4e3af99e4..f72f091383 100644 --- a/packages/frontend/src/components/MkDivider.vue +++ b/packages/frontend/src/components/MkDivider.vue @@ -27,6 +27,6 @@ defineProps<{ <style scoped lang="scss"> .default { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/components/MkDonation.vue b/packages/frontend/src/components/MkDonation.vue index 1dfdebf0d4..43d2002204 100644 --- a/packages/frontend/src/components/MkDonation.vue +++ b/packages/frontend/src/components/MkDonation.vue @@ -75,22 +75,22 @@ function neverShow() { .root { position: fixed; z-index: v-bind(zIndex); - bottom: var(--margin); + bottom: var(--MI-margin); left: 0; right: 0; margin: auto; box-sizing: border-box; - width: calc(100% - (var(--margin) * 2)); + width: calc(100% - (var(--MI-margin) * 2)); max-width: 500px; display: flex; - backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .icon { text-align: center; padding-top: 25px; width: 100px; - color: var(--accent); + color: var(--MI_THEME-accent); } @media (max-width: 500px) { .icon { diff --git a/packages/frontend/src/components/MkDrive.file.vue b/packages/frontend/src/components/MkDrive.file.vue index 20ad2984d8..5dee448329 100644 --- a/packages/frontend/src/components/MkDrive.file.vue +++ b/packages/frontend/src/components/MkDrive.file.vue @@ -112,7 +112,7 @@ function onDragend() { position: relative; padding: 8px 0 0 0; min-height: 180px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); cursor: pointer; &:hover { @@ -152,14 +152,14 @@ function onDragend() { } &.isSelected { - background: var(--accent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); + background: var(--MI_THEME-accentDarken); } > .label { @@ -248,7 +248,7 @@ function onDragend() { font-size: 0.8em; text-align: center; word-break: break-all; - color: var(--fg); + color: var(--MI_THEME-fg); overflow: hidden; } </style> diff --git a/packages/frontend/src/components/MkDrive.folder.vue b/packages/frontend/src/components/MkDrive.folder.vue index 44788a6ffb..8496890f60 100644 --- a/packages/frontend/src/components/MkDrive.folder.vue +++ b/packages/frontend/src/components/MkDrive.folder.vue @@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, defineAsyncComponent, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import type { MenuItem } from '@/types/menu.js'; const props = withDefaults(defineProps<{ folder: Misskey.entities.DriveFolder; @@ -313,8 +313,8 @@ function onContextmenu(ev: MouseEvent) { position: relative; padding: 8px; height: 64px; - background: var(--driveFolderBg); - border-radius: var(--radius-xs); + background: var(--MI_THEME-driveFolderBg); + border-radius: var(--MI-radius-xs); cursor: pointer; &.draghover { @@ -326,8 +326,8 @@ function onContextmenu(ev: MouseEvent) { right: -4px; bottom: -4px; left: -4px; - border: 2px dashed var(--focus); - border-radius: var(--radius-xs); + border: 2px dashed var(--MI_THEME-focus); + border-radius: var(--MI-radius-xs); } } } @@ -345,13 +345,13 @@ function onContextmenu(ev: MouseEvent) { width: 18px; height: 18px; background: #fff; - border: solid 2px var(--divider); + border: solid 2px var(--MI_THEME-divider); border-radius: 4px; box-sizing: border-box; &.checked { - border-color: var(--accent); - background: var(--accent); + border-color: var(--MI_THEME-accent); + background: var(--MI_THEME-accent); &::after { content: "\ea5e"; @@ -368,14 +368,13 @@ function onContextmenu(ev: MouseEvent) { } &:hover { - background: var(--accentedBg); + background: var(--MI_THEME-accentedBg); } } .name { margin: 0; font-size: 0.9em; - color: var(--desktopDriveFolderFg); } .icon { @@ -388,6 +387,5 @@ function onContextmenu(ev: MouseEvent) { margin: 4px 4px; font-size: 0.8em; text-align: right; - color: var(--desktopDriveFolderFg); } </style> diff --git a/packages/frontend/src/components/MkDrive.vue b/packages/frontend/src/components/MkDrive.vue index a471457b44..5a0803f1e3 100644 --- a/packages/frontend/src/components/MkDrive.vue +++ b/packages/frontend/src/components/MkDrive.vue @@ -167,7 +167,12 @@ const ilFilesObserver = new IntersectionObserver( (entries) => entries.some((entry) => entry.isIntersecting) && !fetching.value && moreFiles.value && fetchMoreFiles(), ); +const sortModeSelect = ref<NonNullable<Misskey.entities.DriveFilesRequest['sort']>>('+createdAt'); + watch(folder, () => emit('cd', folder.value)); +watch(sortModeSelect, () => { + fetch(); +}); function onStreamDriveFileCreated(file: Misskey.entities.DriveFile) { addFile(file, true); @@ -203,7 +208,7 @@ function onStreamDriveFolderDeleted(folderId: string) { removeFolder(folderId); } -function onDragover(ev: DragEvent): any { +function onDragover(ev: DragEvent) { if (!ev.dataTransfer) return; // ドラッグ元ãŒè‡ªåˆ†è‡ªèº«ã®æ‰€æœ‰ã™ã‚‹ã‚¢ã‚¤ãƒ†ãƒ ã ã£ãŸã‚‰ @@ -248,7 +253,7 @@ function onDragleave() { draghover.value = false; } -function onDrop(ev: DragEvent): any { +function onDrop(ev: DragEvent) { draghover.value = false; if (!ev.dataTransfer) return; @@ -337,7 +342,7 @@ function createFolder() { title: i18n.ts.createFolder, placeholder: i18n.ts.folderName, }).then(({ canceled, result: name }) => { - if (canceled) return; + if (canceled || name == null) return; misskeyApi('drive/folders/create', { name: name, parentId: folder.value ? folder.value.id : undefined, @@ -570,6 +575,7 @@ async function fetch() { type: props.type, limit: filesMax + 1, searchQuery: searchQuery.value.toString().trim(), + sort: sortModeSelect.value, }).then(fetchedFiles => { if (fetchedFiles.length === filesMax + 1) { moreFiles.value = true; @@ -621,6 +627,7 @@ function fetchMoreFiles() { untilId: files.value.at(-1)?.id, limit: max + 1, searchQuery: searchQuery.value.toString().trim(), + sort: sortModeSelect.value, }).then(files => { if (files.length === max + 1) { moreFiles.value = true; @@ -656,6 +663,43 @@ function getMenu() { type: 'label', }); + menu.push({ + type: 'parent', + text: i18n.ts.sort, + icon: 'ti ti-arrows-sort', + children: [{ + text: `${i18n.ts.registeredDate} (${i18n.ts.descendingOrder})`, + icon: 'ti ti-sort-descending-letters', + action: () => { sortModeSelect.value = '+createdAt'; }, + active: sortModeSelect.value === '+createdAt', + }, { + text: `${i18n.ts.registeredDate} (${i18n.ts.ascendingOrder})`, + icon: 'ti ti-sort-ascending-letters', + action: () => { sortModeSelect.value = '-createdAt'; }, + active: sortModeSelect.value === '-createdAt', + }, { + text: `${i18n.ts.size} (${i18n.ts.descendingOrder})`, + icon: 'ti ti-sort-descending-letters', + action: () => { sortModeSelect.value = '+size'; }, + active: sortModeSelect.value === '+size', + }, { + text: `${i18n.ts.size} (${i18n.ts.ascendingOrder})`, + icon: 'ti ti-sort-ascending-letters', + action: () => { sortModeSelect.value = '-size'; }, + active: sortModeSelect.value === '-size', + }, { + text: `${i18n.ts.name} (${i18n.ts.descendingOrder})`, + icon: 'ti ti-sort-descending-letters', + action: () => { sortModeSelect.value = '+name'; }, + active: sortModeSelect.value === '+name', + }, { + text: `${i18n.ts.name} (${i18n.ts.ascendingOrder})`, + icon: 'ti ti-sort-ascending-letters', + action: () => { sortModeSelect.value = '-name'; }, + active: sortModeSelect.value === '-name', + }], + }); + if (folder.value) { menu.push({ text: i18n.ts.renameFolder, @@ -735,7 +779,7 @@ onBeforeUnmount(() => { box-sizing: border-box; overflow: auto; font-size: 0.9em; - box-shadow: 0 1px 0 var(--divider); + box-shadow: 0 1px 0 var(--MI_THEME-divider); user-select: none; } @@ -787,7 +831,7 @@ onBeforeUnmount(() => { .main { flex: 1; overflow: auto; - padding: var(--margin); + padding: var(--MI-margin); user-select: none; &.fetching { @@ -834,7 +878,7 @@ onBeforeUnmount(() => { top: 38px; width: 100%; height: calc(100% - 38px); - border: dashed 2px var(--focus); + border: dashed 2px var(--MI_THEME-focus); pointer-events: none; } </style> diff --git a/packages/frontend/src/components/MkDriveFileThumbnail.vue b/packages/frontend/src/components/MkDriveFileThumbnail.vue index 543cf24022..1079e52030 100644 --- a/packages/frontend/src/components/MkDriveFileThumbnail.vue +++ b/packages/frontend/src/components/MkDriveFileThumbnail.vue @@ -69,8 +69,8 @@ const isThumbnailAvailable = computed(() => { .root { position: relative; display: flex; - background: var(--panel); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); overflow: clip; } @@ -83,7 +83,7 @@ const isThumbnailAvailable = computed(() => { height: 100%; pointer-events: none; border-radius: inherit; - box-shadow: inset 0 0 0 4px var(--warn); + box-shadow: inset 0 0 0 4px var(--MI_THEME-warn); } .iconSub { diff --git a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue index c060c3a659..6e9eb75920 100644 --- a/packages/frontend/src/components/MkEmbedCodeGenDialog.vue +++ b/packages/frontend/src/components/MkEmbedCodeGenDialog.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only :scroll="false" :withOkButton="false" @close="cancel()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header><i class="ti ti-code"></i> {{ i18n.ts._embedCodeGen.title }}</template> @@ -306,9 +306,9 @@ onUnmounted(() => { .embedCodeGenPreviewRoot { position: relative; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); background-size: auto auto; - background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--panel) 6px, var(--panel) 12px); + background-image: repeating-linear-gradient(135deg, transparent, transparent 6px, var(--MI_THEME-panel) 6px, var(--MI_THEME-panel) 12px); cursor: not-allowed; } @@ -381,8 +381,8 @@ onUnmounted(() => { .embedCodeGenResultHeadingIcon { margin: 0 auto; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); text-align: center; height: 64px; width: 64px; diff --git a/packages/frontend/src/components/MkEmojiPicker.section.vue b/packages/frontend/src/components/MkEmojiPicker.section.vue index 151843b18c..a4f763e895 100644 --- a/packages/frontend/src/components/MkEmojiPicker.section.vue +++ b/packages/frontend/src/components/MkEmojiPicker.section.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <!-- ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆã®è¦ç´ ã®classã¯è¦ªã‹ã‚‰åˆ©ç”¨ã•れるã®ã§ã‚€ã‚„ã¿ã«å¼„らãªã„ã“㨠--> <!-- フォルダã®ä¸ã«ã¯ã‚«ã‚¹ã‚¿ãƒ 絵文å—ã ã‘(Unicode絵文å—ã‚‚ã“ã£ã¡ï¼‰ --> -<section v-if="!hasChildSection" v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);"> +<section v-if="!hasChildSection" v-panel style="border-radius: var(--MI-radius-sm); border-bottom: 0.5px solid var(--MI_THEME-divider);"> <header class="_acrylic" @click="shown = !shown"> <i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ph-smiley-sticker ph-bold ph-lg"></i>:{{ emojis.length }}) </header> @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </section> <!-- フォルダã®ä¸ã«ã¯ã‚«ã‚¹ã‚¿ãƒ 絵文å—やフォルダãŒã‚ã‚‹ --> -<section v-else v-panel style="border-radius: var(--radius-sm); border-bottom: 0.5px solid var(--divider);"> +<section v-else v-panel style="border-radius: var(--MI-radius-sm); border-bottom: 0.5px solid var(--MI_THEME-divider);"> <header class="_acrylic" @click="shown = !shown"> <i class="toggle ti-fw" :class="shown ? 'ti ti-chevron-down' : 'ti ti-chevron-up'"></i> <slot></slot> (<i class="ti ti-folder ti-fw"></i>:{{ customEmojiTree?.length }} <i class="ph-smiley-sticker ph-bold ph-lg ti-fw"></i>:{{ emojis.length }}) </header> @@ -90,7 +90,7 @@ function computeButtonTitle(ev: MouseEvent): void { elm.title = getEmojiName(emoji); } -function nestedChosen(emoji: any, ev: MouseEvent) { +function nestedChosen(emoji: string, ev: MouseEvent) { emit('chosen', emoji, ev); } </script> diff --git a/packages/frontend/src/components/MkEmojiPicker.vue b/packages/frontend/src/components/MkEmojiPicker.vue index 949ed4db91..dc589a28e0 100644 --- a/packages/frontend/src/components/MkEmojiPicker.vue +++ b/packages/frontend/src/components/MkEmojiPicker.vue @@ -409,7 +409,7 @@ function computeButtonTitle(ev: MouseEvent): void { elm.title = getEmojiName(emoji); } -function chosen(emoji: any, ev?: MouseEvent) { +function chosen(emoji: string | Misskey.entities.EmojiSimple | UnicodeEmojiDef, ev?: MouseEvent) { const el = ev && (ev.currentTarget ?? ev.target) as HTMLElement | null | undefined; if (el) { const rect = el.getBoundingClientRect(); @@ -426,7 +426,7 @@ function chosen(emoji: any, ev?: MouseEvent) { // 最近使ã£ãŸçµµæ–‡å—æ›´æ–° if (!pinned.value?.includes(key)) { let recents = defaultStore.state.recentlyUsedEmojis; - recents = recents.filter((emoji: any) => emoji !== key); + recents = recents.filter((emoji) => emoji !== key); recents.unshift(key); defaultStore.set('recentlyUsedEmojis', recents.splice(0, 32)); } @@ -580,7 +580,7 @@ defineExpose({ &:disabled { cursor: not-allowed; - background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%); opacity: 1; > .emoji { @@ -615,7 +615,7 @@ defineExpose({ &:disabled { cursor: not-allowed; - background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%); opacity: 1; > .emoji { @@ -638,7 +638,7 @@ defineExpose({ outline: none; border: none; background: transparent; - color: var(--fg); + color: var(--MI_THEME-fg); &:not(:focus):not(.filled) { margin-bottom: env(safe-area-inset-bottom, 0px); @@ -647,7 +647,7 @@ defineExpose({ &:not(.filled) { order: 1; z-index: 2; - box-shadow: 0px -1px 0 0px var(--divider); + box-shadow: 0px -1px 0 0px var(--MI_THEME-divider); } } @@ -658,11 +658,11 @@ defineExpose({ > .tab { flex: 1; height: 38px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); &.active { - border-top: solid 1px var(--accent); - color: var(--accent); + border-top: solid 1px var(--MI_THEME-accent); + color: var(--MI_THEME-accent); } } } @@ -681,7 +681,7 @@ defineExpose({ > .group { &:not(.index) { padding: 4px 0 8px 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > header { @@ -708,7 +708,7 @@ defineExpose({ cursor: pointer; &:hover { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -722,7 +722,7 @@ defineExpose({ width: var(--eachSize); height: var(--eachSize); contain: strict; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); font-size: 24px; &:hover { @@ -730,13 +730,13 @@ defineExpose({ } &:active { - background: var(--accent); + background: var(--MI_THEME-accent); box-shadow: inset 0 0.15em 0.3em rgba(27, 31, 35, 0.15); } &:disabled { cursor: not-allowed; - background: linear-gradient(-45deg, transparent 0% 48%, var(--X6) 48% 52%, transparent 52% 100%); + background: linear-gradient(-45deg, transparent 0% 48%, var(--MI_THEME-X6) 48% 52%, transparent 52% 100%); opacity: 1; > .emoji { @@ -757,7 +757,7 @@ defineExpose({ } &.result { - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); &:empty { display: none; diff --git a/packages/frontend/src/components/MkEmojiPickerDialog.vue b/packages/frontend/src/components/MkEmojiPickerDialog.vue index 18e80d8445..3178f72498 100644 --- a/packages/frontend/src/components/MkEmojiPickerDialog.vue +++ b/packages/frontend/src/components/MkEmojiPickerDialog.vue @@ -87,7 +87,7 @@ function opening() { <style lang="scss" module> .drawer { - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); border-bottom-right-radius: 0; border-bottom-left-radius: 0; } diff --git a/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts new file mode 100644 index 0000000000..6763f7c546 --- /dev/null +++ b/packages/frontend/src/components/MkExtensionInstaller.stories.impl.ts @@ -0,0 +1,83 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import { StoryObj } from '@storybook/vue3'; +import MkExtensionInstaller from './MkExtensionInstaller.vue'; +import lightTheme from '@@/themes/_light.json5'; + +export const Plugin = { + render(args) { + return { + components: { + MkExtensionInstaller, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkExtensionInstaller v-bind="props" />', + }; + }, + args: { + extension: { + type: 'plugin', + raw: '"do nothing"', + meta: { + name: 'do nothing plugin', + version: '1.0', + author: 'syuilo and misskey-project', + description: 'a plugin that does nothing', + permissions: ['read:account'], + config: { + 'doNothing': true, + }, + }, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkExtensionInstaller>; + +export const Theme = { + render(args) { + return { + components: { + MkExtensionInstaller, + }, + setup() { + return { + args, + }; + }, + computed: { + props() { + return { + ...this.args, + }; + }, + }, + template: '<MkExtensionInstaller v-bind="props" />', + }; + }, + args: { + extension: { + type: 'theme', + raw: JSON.stringify(lightTheme), + meta: lightTheme, + }, + }, + parameters: { + layout: 'centered', + }, +} satisfies StoryObj<typeof MkExtensionInstaller>; diff --git a/packages/frontend/src/components/MkExtensionInstaller.vue b/packages/frontend/src/components/MkExtensionInstaller.vue new file mode 100644 index 0000000000..d59b20435e --- /dev/null +++ b/packages/frontend/src/components/MkExtensionInstaller.vue @@ -0,0 +1,146 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div class="_gaps_m" :class="$style.extInstallerRoot"> + <div :class="$style.extInstallerIconWrapper"> + <i v-if="isPlugin" class="ti ti-plug"></i> + <i v-else-if="isTheme" class="ti ti-palette"></i> + <!-- 拡張用? --> + <i v-else class="ti ti-download"></i> + </div> + <h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].title }}</h2> + <div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div> + <MkInfo v-if="isPlugin" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo> + <FormSection> + <template #label>{{ i18n.ts._externalResourceInstaller[`_${extension.type}`].metaTitle }}</template> + <div class="_gaps_s"> + <FormSplit> + <MkKeyValue> + <template #key>{{ i18n.ts.name }}</template> + <template #value>{{ extension.meta.name }}</template> + </MkKeyValue> + <MkKeyValue> + <template #key>{{ i18n.ts.author }}</template> + <template #value>{{ extension.meta.author }}</template> + </MkKeyValue> + </FormSplit> + <MkKeyValue v-if="isPlugin"> + <template #key>{{ i18n.ts.description }}</template> + <template #value>{{ extension.meta.description ?? i18n.ts.none }}</template> + </MkKeyValue> + <MkKeyValue v-if="isPlugin"> + <template #key>{{ i18n.ts.version }}</template> + <template #value>{{ extension.meta.version }}</template> + </MkKeyValue> + <MkKeyValue v-if="isPlugin"> + <template #key>{{ i18n.ts.permission }}</template> + <template #value> + <ul v-if="extension.meta.permissions && extension.meta.permissions.length > 0" :class="$style.extInstallerKVList"> + <li v-for="permission in extension.meta.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> + </ul> + <template v-else>{{ i18n.ts.none }}</template> + </template> + </MkKeyValue> + <MkKeyValue v-if="isTheme"> + <template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template> + <template #value>{{ i18n.ts[extension.meta.base ?? 'none'] }}</template> + </MkKeyValue> + <MkFolder> + <template #icon><i class="ti ti-code"></i></template> + <template #label>{{ i18n.ts._plugin.viewSource }}</template> + + <MkCode :code="extension.raw"/> + </MkFolder> + </div> + </FormSection> + <slot name="additionalInfo"/> + <div class="_buttonsCenter"> + <MkButton primary @click="emits('confirm')"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton> + </div> +</div> +</template> + +<script lang="ts"> +export type Extension = { + type: 'plugin'; + raw: string; + meta: { + name: string; + version: string; + author: string; + description?: string; + permissions?: string[]; + config?: Record<string, unknown>; + }; +} | { + type: 'theme'; + raw: string; + meta: { + name: string; + author: string; + base?: 'light' | 'dark'; + }; +}; +</script> +<script lang="ts" setup> +import { computed } from 'vue'; +import MkButton from '@/components/MkButton.vue'; +import FormSection from '@/components/form/section.vue'; +import FormSplit from '@/components/form/split.vue'; +import MkCode from '@/components/MkCode.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import MkKeyValue from '@/components/MkKeyValue.vue'; +import { i18n } from '@/i18n.js'; + +const isPlugin = computed(() => props.extension.type === 'plugin'); +const isTheme = computed(() => props.extension.type === 'theme'); + +const props = defineProps<{ + extension: Extension; +}>(); + +const emits = defineEmits<{ + (ev: 'confirm'): void; +}>(); +</script> + +<style lang="scss" module> +.extInstallerRoot { + border-radius: var(--MI-radius); + background: var(--MI_THEME-panel); + padding: 1.5rem; +} + +.extInstallerIconWrapper { + width: 48px; + height: 48px; + font-size: 24px; + line-height: 48px; + text-align: center; + border-radius: 50%; + margin-left: auto; + margin-right: auto; + + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); +} + +.extInstallerTitle { + font-size: 1.2rem; + text-align: center; + margin: 0; +} + +.extInstallerNormDesc { + text-align: center; +} + +.extInstallerKVList { + margin-top: 0; + margin-bottom: 0; +} +</style> diff --git a/packages/frontend/src/components/MkFileListForAdmin.vue b/packages/frontend/src/components/MkFileListForAdmin.vue index 7af68a32ba..204bf69f48 100644 --- a/packages/frontend/src/components/MkFileListForAdmin.vue +++ b/packages/frontend/src/components/MkFileListForAdmin.vue @@ -66,7 +66,7 @@ const props = defineProps<{ align-items: center; &:hover { - color: var(--accent); + color: var(--MI_THEME-accent); } > .thumbnail { @@ -108,7 +108,7 @@ const props = defineProps<{ padding: 2px 4px; background: #ff0000bf; color: #fff; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); font-size: 85%; animation: sensitive-blink 1s infinite; } diff --git a/packages/frontend/src/components/MkFlashPreview.vue b/packages/frontend/src/components/MkFlashPreview.vue index 85f7917658..0bb1450f87 100644 --- a/packages/frontend/src/components/MkFlashPreview.vue +++ b/packages/frontend/src/components/MkFlashPreview.vue @@ -35,7 +35,7 @@ const props = defineProps<{ &:hover { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); } &:focus-visible { @@ -82,7 +82,6 @@ const props = defineProps<{ > p { display: inline-block; margin: 0; - color: var(--urlPreviewInfo); font-size: 0.8em; line-height: 16px; vertical-align: top; @@ -91,7 +90,7 @@ const props = defineProps<{ } &:global(.gray) { - --c: var(--bg); + --c: var(--MI_THEME-bg); background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); background-size: 16px 16px; } diff --git a/packages/frontend/src/components/MkFoldableSection.vue b/packages/frontend/src/components/MkFoldableSection.vue index f10d58b38a..5bf3fdfe76 100644 --- a/packages/frontend/src/components/MkFoldableSection.vue +++ b/packages/frontend/src/components/MkFoldableSection.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div ref="rootEl" :class="$style.root"> - <header :class="$style.header" class="_button" :style="{ background: bg }" @click="showBody = !showBody"> + <header :class="$style.header" class="_button" @click="showBody = !showBody"> <div :class="$style.title"><div><slot name="header"></slot></div></div> <div :class="$style.divider"></div> <button class="_button" :class="$style.button"> @@ -32,21 +32,23 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onMounted, ref, shallowRef, watch } from 'vue'; -import tinycolor from 'tinycolor2'; import { miLocalStorage } from '@/local-storage.js'; import { defaultStore } from '@/store.js'; +import { getBgColor } from '@/scripts/get-bg-color.js'; const miLocalStoragePrefix = 'ui:folder:' as const; const props = withDefaults(defineProps<{ expanded?: boolean; - persistKey?: string; + persistKey?: string | null; }>(), { expanded: true, + persistKey: null, }); -const rootEl = shallowRef<HTMLDivElement>(); -const bg = ref<string>(); +const rootEl = shallowRef<HTMLElement>(); +const parentBg = ref<string | null>(null); +// eslint-disable-next-line vue/no-setup-props-reactivity-loss const showBody = ref((props.persistKey && miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`)) ? (miLocalStorage.getItem(`${miLocalStoragePrefix}${props.persistKey}`) === 't') : props.expanded); watch(showBody, () => { @@ -55,47 +57,34 @@ watch(showBody, () => { } }); -function enter(element: Element) { - const el = element as HTMLElement; +function enter(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; el.style.height = '0'; el.offsetHeight; // reflow - el.style.height = elementHeight + 'px'; + el.style.height = `${elementHeight}px`; } -function afterEnter(element: Element) { - const el = element as HTMLElement; - el.style.height = 'unset'; +function afterEnter(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } -function leave(element: Element) { - const el = element as HTMLElement; +function leave(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; - el.style.height = elementHeight + 'px'; + el.style.height = `${elementHeight}px`; el.offsetHeight; // reflow el.style.height = '0'; } -function afterLeave(element: Element) { - const el = element as HTMLElement; - el.style.height = 'unset'; +function afterLeave(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } onMounted(() => { - function getParentBg(el?: HTMLElement | null): string { - if (el == null || el.tagName === 'BODY') return 'var(--bg)'; - const background = el.style.background || el.style.backgroundColor; - if (background) { - return background; - } else { - return getParentBg(el.parentElement); - } - } - - const rawBg = getParentBg(rootEl.value); - const _bg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); - _bg.setAlpha(0.85); - bg.value = _bg.toRgbString(); + parentBg.value = getBgColor(rootEl.value?.parentElement); }); </script> @@ -118,9 +107,10 @@ onMounted(() => { position: relative; z-index: 10; position: sticky; - top: var(--stickyTop, 0px); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(20px)); + top: var(--MI-stickyTop, 0px); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(20px)); + background-color: color(from v-bind("parentBg ?? 'var(--MI_THEME-bg)'") srgb r g b / 0.85); } .title { @@ -134,7 +124,7 @@ onMounted(() => { flex: 1; margin: auto; height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .button { diff --git a/packages/frontend/src/components/MkFolder.vue b/packages/frontend/src/components/MkFolder.vue index 392963fdb9..0b4114d252 100644 --- a/packages/frontend/src/components/MkFolder.vue +++ b/packages/frontend/src/components/MkFolder.vue @@ -38,9 +38,12 @@ SPDX-License-Identifier: AGPL-3.0-only > <KeepAlive> <div v-show="opened"> - <MkSpacer :marginMin="14" :marginMax="22"> + <MkSpacer v-if="withSpacer" :marginMin="14" :marginMax="22"> <slot></slot> </MkSpacer> + <div v-else> + <slot></slot> + </div> <div v-if="$slots.footer" :class="$style.footer"> <slot name="footer"></slot> </div> @@ -53,51 +56,49 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { nextTick, onMounted, shallowRef, ref } from 'vue'; +import { nextTick, onMounted, ref, shallowRef } from 'vue'; import { defaultStore } from '@/store.js'; +import { getBgColor } from '@/scripts/get-bg-color.js'; const props = withDefaults(defineProps<{ defaultOpen?: boolean; maxHeight?: number | null; + withSpacer?: boolean; }>(), { defaultOpen: false, maxHeight: null, + withSpacer: true, }); -const getBgColor = (el: HTMLElement) => { - const style = window.getComputedStyle(el); - if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { - return style.backgroundColor; - } else { - return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; - } -}; - const rootEl = shallowRef<HTMLElement>(); const bgSame = ref(false); const opened = ref(props.defaultOpen); const openedAtLeastOnce = ref(props.defaultOpen); -function enter(el) { +function enter(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; - el.style.height = 0; + el.style.height = '0'; el.offsetHeight; // reflow - el.style.height = Math.min(elementHeight, props.maxHeight ?? Infinity) + 'px'; + el.style.height = `${Math.min(elementHeight, props.maxHeight ?? Infinity)}px`; } -function afterEnter(el) { - el.style.height = null; +function afterEnter(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } -function leave(el) { +function leave(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementHeight = el.getBoundingClientRect().height; - el.style.height = elementHeight + 'px'; + el.style.height = `${elementHeight}px`; el.offsetHeight; // reflow - el.style.height = 0; + el.style.height = '0'; } -function afterLeave(el) { - el.style.height = null; +function afterLeave(el: Element) { + if (!(el instanceof HTMLElement)) return; + el.style.height = ''; } function toggle() { @@ -112,8 +113,8 @@ function toggle() { onMounted(() => { const computedStyle = getComputedStyle(document.documentElement); - const parentBg = getBgColor(rootEl.value!.parentElement!); - const myBg = computedStyle.getPropertyValue('--panel'); + const parentBg = getBgColor(rootEl.value?.parentElement) ?? 'transparent'; + const myBg = computedStyle.getPropertyValue('--MI_THEME-panel'); bgSame.value = parentBg === myBg; }); </script> @@ -139,15 +140,15 @@ onMounted(() => { width: 100%; box-sizing: border-box; padding: 9px 12px 9px 12px; - background: var(--folderHeaderBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-radius: var(--radius-sm); + background: var(--MI_THEME-folderHeaderBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + border-radius: var(--MI-radius-sm); transition: border-radius 0.3s; &:hover { text-decoration: none; - background: var(--folderHeaderHoverBg); + background: var(--MI_THEME-folderHeaderHoverBg); } &:focus-within { @@ -155,12 +156,12 @@ onMounted(() => { } &.active { - color: var(--accent); - background: var(--folderHeaderHoverBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-folderHeaderHoverBg); } &.opened { - border-radius: var(--radius-sm) var(--radius-sm) 0 0; + border-radius: var(--MI-radius-sm) var(--MI-radius-sm) 0 0; } } @@ -170,7 +171,7 @@ onMounted(() => { } .headerLower { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: .85em; padding-left: 4px; } @@ -204,13 +205,13 @@ onMounted(() => { } .headerTextSub { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: .85em; } .headerRight { margin-left: auto; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); white-space: nowrap; } @@ -219,26 +220,26 @@ onMounted(() => { } .body { - background: var(--panel); - border-radius: 0 0 var(--radius-sm) var(--radius-sm); + background: var(--MI_THEME-panel); + border-radius: 0 0 var(--MI-radius-sm) var(--MI-radius-sm); container-type: inline-size; &.bgSame { - background: var(--bg); + background: var(--MI_THEME-bg); } } .footer { position: sticky !important; z-index: 1; - bottom: var(--stickyBottom, 0px); + bottom: var(--MI-stickyBottom, 0px); left: 0; padding: 12px; - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); background-size: auto auto; - background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--panel) 5px, var(--panel) 10px); + background-image: repeating-linear-gradient(135deg, transparent, transparent 5px, var(--MI_THEME-panel) 5px, var(--MI_THEME-panel) 10px); border-radius: 0 0 6px 6px; } </style> diff --git a/packages/frontend/src/components/MkFollowButton.vue b/packages/frontend/src/components/MkFollowButton.vue index 52497a2994..c7965aaac4 100644 --- a/packages/frontend/src/components/MkFollowButton.vue +++ b/packages/frontend/src/components/MkFollowButton.vue @@ -37,13 +37,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onBeforeUnmount, onMounted, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { host } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { useStream } from '@/stream.js'; import { i18n } from '@/i18n.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { pleaseLogin } from '@/scripts/please-login.js'; -import { host } from '@@/js/config.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; @@ -80,7 +80,7 @@ function onFollowChange(user: Misskey.entities.UserDetailed) { } async function onClick() { - pleaseLogin(undefined, { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` }); + pleaseLogin({ openOnRemote: { type: 'web', path: `/@${props.user.username}@${props.user.host ?? host}` } }); wait.value = true; @@ -91,7 +91,10 @@ async function onClick() { text: i18n.tsx.unfollowConfirm({ name: props.user.name || props.user.username }), }); - if (canceled) return; + if (canceled) { + wait.value = false; + return; + } await misskeyApi('following/delete', { userId: props.user.id, @@ -125,7 +128,10 @@ async function onClick() { }); hasPendingFollowRequestFromYou.value = true; - if ($i == null) return; + if ($i == null) { + wait.value = false; + return; + } claimAchievement('following1'); @@ -165,12 +171,12 @@ onBeforeUnmount(() => { position: relative; display: inline-block; font-weight: bold; - color: var(--fgOnWhite); - border: solid 1px var(--accent); + color: var(--MI_THEME-fgOnWhite); + border: solid 1px var(--MI_THEME-accent); padding: 0; height: 31px; font-size: 16px; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); background: #fff; &.full { @@ -201,17 +207,17 @@ onBeforeUnmount(() => { } &.active { - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:hover { - background: var(--accentLighten); - border-color: var(--accentLighten); + background: var(--MI_THEME-accentLighten); + border-color: var(--MI_THEME-accentLighten); } &:active { - background: var(--accentDarken); - border-color: var(--accentDarken); + background: var(--MI_THEME-accentDarken); + border-color: var(--MI_THEME-accentDarken); } } diff --git a/packages/frontend/src/components/MkFormDialog.file.vue b/packages/frontend/src/components/MkFormDialog.file.vue index 9360594236..ecb6cf882b 100644 --- a/packages/frontend/src/components/MkFormDialog.file.vue +++ b/packages/frontend/src/components/MkFormDialog.file.vue @@ -66,6 +66,6 @@ function selectButton(ev: MouseEvent) { <style module> .fileNotSelected { font-weight: 700; - color: var(--infoWarnFg); + color: var(--MI_THEME-infoWarnFg); } </style> diff --git a/packages/frontend/src/components/MkFormDialog.vue b/packages/frontend/src/components/MkFormDialog.vue index 124f114111..a639eae208 100644 --- a/packages/frontend/src/components/MkFormDialog.vue +++ b/packages/frontend/src/components/MkFormDialog.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only @click="cancel()" @ok="ok()" @close="cancel()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> {{ title }} diff --git a/packages/frontend/src/components/MkFormFooter.vue b/packages/frontend/src/components/MkFormFooter.vue index 1e88d59d8e..f409f6ce50 100644 --- a/packages/frontend/src/components/MkFormFooter.vue +++ b/packages/frontend/src/components/MkFormFooter.vue @@ -36,7 +36,7 @@ const props = defineProps<{ } .text { - color: var(--warn); + color: var(--MI_THEME-warn); font-size: 90%; animation: modified-blink 2s infinite; } diff --git a/packages/frontend/src/components/MkFukidashi.vue b/packages/frontend/src/components/MkFukidashi.vue new file mode 100644 index 0000000000..8b1c56fca4 --- /dev/null +++ b/packages/frontend/src/components/MkFukidashi.vue @@ -0,0 +1,100 @@ +<!-- +SPDX-FileCopyrightText: syuilo and other misskey contributors +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div + :class="[ + $style.root, + tail === 'left' ? $style.left : $style.right, + negativeMargin === true && $style.negativeMargin, + shadow === true && $style.shadow, + ]" +> + <div :class="$style.bg"> + <svg v-if="tail !== 'none'" :class="$style.tail" version="1.1" viewBox="0 0 14.597 14.58" xmlns="http://www.w3.org/2000/svg"> + <g transform="translate(-173.71 -87.184)"> + <path d="m188.19 87.657c-1.469 2.3218-3.9315 3.8312-6.667 4.0865-2.2309-1.7379-4.9781-2.6816-7.8061-2.6815h-5.1e-4v12.702h12.702v-5.1e-4c2e-5 -1.9998-0.47213-3.9713-1.378-5.754 2.0709-1.6834 3.2732-4.2102 3.273-6.8791-6e-5 -0.49375-0.0413-0.98662-0.1235-1.4735z" fill-rule="evenodd" stroke-linecap="round" stroke-linejoin="round" stroke-width=".33225" style="paint-order:stroke fill markers"/> + </g> + </svg> + <div :class="$style.content"> + <slot></slot> + </div> + </div> +</div> +</template> + +<script setup lang="ts"> +withDefaults(defineProps<{ + tail?: 'left' | 'right' | 'none'; + negativeMargin?: boolean; + shadow?: boolean; +}>(), { + tail: 'right', + negativeMargin: false, + shadow: false, +}); +</script> + +<style module lang="scss"> +.root { + --fukidashi-radius: var(--MI-radius); + --fukidashi-bg: var(--MI_THEME-panel); + + position: relative; + display: inline-block; + min-height: calc(var(--fukidashi-radius) * 2); + padding-top: calc(var(--fukidashi-radius) * .13); + + &.shadow { + filter: drop-shadow(0 4px 32px var(--MI_THEME-shadow)); + } + + &.left { + padding-left: calc(var(--fukidashi-radius) * .13); + + &.negativeMargin { + margin-left: calc(calc(var(--fukidashi-radius) * .13) * -1); + } + } + + &.right { + padding-right: calc(var(--fukidashi-radius) * .13); + + &.negativeMargin { + margin-right: calc(calc(var(--fukidashi-radius) * .13) * -1); + } + } +} + +.bg { + width: 100%; + height: 100%; + background: var(--fukidashi-bg); + border-radius: var(--fukidashi-radius); +} + +.content { + position: relative; + padding: 8px 12px; +} + +.tail { + position: absolute; + top: 0; + display: block; + width: calc(var(--fukidashi-radius) * 1.13); + height: auto; + fill: var(--fukidashi-bg); +} + +.left .tail { + left: 0; + transform: rotateY(180deg); +} + +.right .tail { + right: 0; +} +</style> diff --git a/packages/frontend/src/components/MkGalleryPostPreview.vue b/packages/frontend/src/components/MkGalleryPostPreview.vue index 2bb5b8762a..22f8355acf 100644 --- a/packages/frontend/src/components/MkGalleryPostPreview.vue +++ b/packages/frontend/src/components/MkGalleryPostPreview.vue @@ -75,7 +75,7 @@ function leaveHover(): void { &:hover { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); > .thumbnail { transform: scale(1.1); diff --git a/packages/frontend/src/components/MkGoogle.vue b/packages/frontend/src/components/MkGoogle.vue index 54c7585f18..6cdab2479e 100644 --- a/packages/frontend/src/components/MkGoogle.vue +++ b/packages/frontend/src/components/MkGoogle.vue @@ -41,8 +41,8 @@ const search = () => { width: 100%; height: 40px; font-size: 16px; - border: solid 1px var(--divider); - border-radius: var(--radius-xs) 0 0 var(--radius-xs); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs) 0 0 var(--MI-radius-xs); -webkit-appearance: textfield; } @@ -50,9 +50,9 @@ const search = () => { flex-shrink: 0; margin: 0; padding: 0 16px; - border: solid 1px var(--divider); + border: solid 1px var(--MI_THEME-divider); border-left: none; - border-radius: 0 var(--radius-xs) var(--radius-xs) 0; + border-radius: 0 var(--MI-radius-xs) var(--MI-radius-xs) 0; &:active { box-shadow: 0 2px 4px rgba(#000, 0.15) inset; diff --git a/packages/frontend/src/components/MkInfo.vue b/packages/frontend/src/components/MkInfo.vue index 46c2db4b1f..bdc38f5142 100644 --- a/packages/frontend/src/components/MkInfo.vue +++ b/packages/frontend/src/components/MkInfo.vue @@ -36,15 +36,15 @@ function close() { align-items: center; padding: 12px 14px; font-size: 90%; - background: color-mix(in srgb, var(--infoBg) 65%, transparent); - color: var(--infoFg); - border-radius: var(--radius); + background: color-mix(in srgb, var(--MI_THEME-infoBg) 65%, transparent); + color: var(--MI_THEME-infoFg); + border-radius: var(--MI-radius); white-space: pre-wrap; z-index: 1; &.warn { - background: color-mix(in srgb, var(--infoWarnBg) 65%, transparent); - color: var(--infoWarnFg); + background: color-mix(in srgb, var(--MI_THEME-infoWarnBg) 65%, transparent); + color: var(--MI_THEME-infoWarnFg); } } diff --git a/packages/frontend/src/components/MkInput.vue b/packages/frontend/src/components/MkInput.vue index 42e1146e27..ec299dce36 100644 --- a/packages/frontend/src/components/MkInput.vue +++ b/packages/frontend/src/components/MkInput.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs } from 'vue'; +import { onMounted, onUnmounted, nextTick, ref, shallowRef, watch, computed, toRefs, InputHTMLAttributes } from 'vue'; import { debounce } from 'throttle-debounce'; import MkButton from '@/components/MkButton.vue'; import { useInterval } from '@@/js/use-interval.js'; @@ -53,7 +53,7 @@ import { Autocomplete, SuggestionType } from '@/scripts/autocomplete.js'; const props = defineProps<{ modelValue: string | number | null; - type?: 'text' | 'number' | 'password' | 'email' | 'url' | 'date' | 'time' | 'search' | 'datetime-local'; + type?: InputHTMLAttributes['type']; required?: boolean; readonly?: boolean; disabled?: boolean; @@ -64,8 +64,8 @@ const props = defineProps<{ mfmAutocomplete?: boolean | SuggestionType[], autocapitalize?: string; spellcheck?: boolean; - inputmode?: 'none' | 'text' | 'search' | 'email' | 'url' | 'numeric' | 'tel' | 'decimal'; - step?: any; + inputmode?: InputHTMLAttributes['inputmode']; + step?: InputHTMLAttributes['step']; datalist?: string[]; min?: number; max?: number | string; @@ -199,7 +199,7 @@ defineExpose({ .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -216,8 +216,8 @@ defineExpose({ &.focused { > .inputCore { - border-color: var(--accent) !important; - //box-shadow: 0 0 0 4px var(--focus); + border-color: var(--MI_THEME-accent) !important; + //box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } @@ -242,17 +242,17 @@ defineExpose({ font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); outline: none; box-shadow: none; box-sizing: border-box; transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } diff --git a/packages/frontend/src/components/MkInstanceCardMini.vue b/packages/frontend/src/components/MkInstanceCardMini.vue index 10b390e7f9..b063b82b17 100644 --- a/packages/frontend/src/components/MkInstanceCardMini.vue +++ b/packages/frontend/src/components/MkInstanceCardMini.vue @@ -46,15 +46,15 @@ function getInstanceIcon(instance): string { display: flex; align-items: center; padding: 16px; - background: var(--panel); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); > :global(.icon) { display: block; width: ($bodyTitleHieght + $bodyInfoHieght); height: ($bodyTitleHieght + $bodyInfoHieght); object-fit: cover; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); margin-right: 10px; } @@ -62,7 +62,7 @@ function getInstanceIcon(instance): string { flex: 1; overflow: hidden; font-size: 0.9em; - color: var(--fg); + color: var(--MI_THEME-fg); padding-right: 8px; > :global(.host) { @@ -109,7 +109,7 @@ function getInstanceIcon(instance): string { } &:global(.gray) { - --c: var(--bg); + --c: var(--MI_THEME-bg); background-image: linear-gradient(45deg, var(--c) 16.67%, transparent 16.67%, transparent 50%, var(--c) 50%, var(--c) 66.67%, transparent 66.67%, transparent 100%); background-size: 16px 16px; } diff --git a/packages/frontend/src/components/MkInstanceStats.vue b/packages/frontend/src/components/MkInstanceStats.vue index d74c885041..8ccbf61e48 100644 --- a/packages/frontend/src/components/MkInstanceStats.vue +++ b/packages/frontend/src/components/MkInstanceStats.vue @@ -121,7 +121,7 @@ function createDoughnut(chartEl, tooltip, data) { labels: data.map(x => x.name), datasets: [{ backgroundColor: data.map(x => x.color), - borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'), + borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'), borderWidth: 2, hoverOffset: 0, data: data.map(x => x.value), @@ -256,8 +256,8 @@ onMounted(() => { flex: 1; min-width: 0; position: relative; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); padding: 24px; max-height: 300px; diff --git a/packages/frontend/src/components/MkInstanceTicker.vue b/packages/frontend/src/components/MkInstanceTicker.vue index 46d42248d3..2a8d5c9f71 100644 --- a/packages/frontend/src/components/MkInstanceTicker.vue +++ b/packages/frontend/src/components/MkInstanceTicker.vue @@ -46,7 +46,7 @@ $height: 2ex; display: flex; align-items: center; height: $height; - border-radius: var(--radius-xs) 0 0 var(--radius-xs); + border-radius: var(--MI-radius-xs) 0 0 var(--MI-radius-xs); overflow: clip; color: #fff; text-shadow: /* .866 ≈ sin(60deg) */ diff --git a/packages/frontend/src/components/MkInviteCode.vue b/packages/frontend/src/components/MkInviteCode.vue index 4aee64f78e..1a71f6574f 100644 --- a/packages/frontend/src/components/MkInviteCode.vue +++ b/packages/frontend/src/components/MkInviteCode.vue @@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ invite.code }}</template> <template #suffix> <span v-if="invite.used">{{ i18n.ts.used }}</span> - <span v-else-if="isExpired" style="color: var(--error)">{{ i18n.ts.expired }}</span> - <span v-else style="color: var(--success)">{{ i18n.ts.unused }}</span> + <span v-else-if="isExpired" style="color: var(--MI_THEME-error)">{{ i18n.ts.expired }}</span> + <span v-else style="color: var(--MI_THEME-success)">{{ i18n.ts.unused }}</span> </template> <template #footer> <div class="_buttons"> diff --git a/packages/frontend/src/components/MkLaunchPad.vue b/packages/frontend/src/components/MkLaunchPad.vue index e0880ec3e7..c7af75e2e7 100644 --- a/packages/frontend/src/components/MkLaunchPad.vue +++ b/packages/frontend/src/components/MkLaunchPad.vue @@ -12,13 +12,13 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="icon" :class="item.icon"></i> <div class="text">{{ item.text }}</div> <span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span> - <span v-else-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> + <span v-else-if="item.indicate" class="indicator _blink"><i class="_indicatorCircle"></i></span> </button> <MkA v-else v-click-anime :to="item.to" class="item" @click.passive="close()"> <i class="icon" :class="item.icon"></i> <div class="text">{{ item.text }}</div> <span v-if="item.indicate && item.indicateValue" class="_indicateCounter indicatorWithValue">{{ item.indicateValue }}</span> - <span v-else-if="item.indicate" class="indicator"><i class="_indicatorCircle"></i></span> + <span v-else-if="item.indicate" class="indicator _blink"><i class="_indicatorCircle"></i></span> </MkA> </template> </div> @@ -77,12 +77,12 @@ function close() { overflow: auto; overscroll-behavior: contain; text-align: left; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); &.asDrawer { width: 100%; padding: 16px 16px max(env(safe-area-inset-bottom, 0px), 16px) 16px; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); border-bottom-right-radius: 0; border-bottom-left-radius: 0; text-align: center; @@ -100,13 +100,13 @@ function close() { justify-content: center; vertical-align: bottom; height: 100px; - border-radius: var(--radius); + border-radius: var(--MI-radius); padding: 10px; box-sizing: border-box; &:hover { - color: var(--accent); - background: var(--accentedBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-accentedBg); text-decoration: none; } @@ -137,9 +137,8 @@ function close() { position: absolute; top: 32px; left: 32px; - color: var(--indicator); + color: var(--MI_THEME-indicator); font-size: 8px; - animation: global-blink 1s infinite; @media (max-width: 500px) { top: 16px; diff --git a/packages/frontend/src/components/MkMediaAudio.vue b/packages/frontend/src/components/MkMediaAudio.vue index 2d7cde1af2..10450fb621 100644 --- a/packages/frontend/src/components/MkMediaAudio.vue +++ b/packages/frontend/src/components/MkMediaAudio.vue @@ -394,8 +394,8 @@ onDeactivated(() => { .audioContainer { container-type: inline-size; position: relative; - border: .5px solid var(--divider); - border-radius: var(--radius); + border: .5px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); overflow: clip; &:focus-visible { @@ -415,7 +415,7 @@ onDeactivated(() => { height: 100%; pointer-events: none; border-radius: inherit; - box-shadow: inset 0 0 0 4px var(--warn); + box-shadow: inset 0 0 0 4px var(--MI_THEME-warn); } } @@ -457,12 +457,12 @@ onDeactivated(() => { .controlButton { padding: 6px; - border-radius: calc(var(--radius) / 2); + border-radius: calc(var(--MI-radius) / 2); font-size: 1.05rem; &:hover { - color: var(--accent); - background-color: var(--accentedBg); + color: var(--MI_THEME-accent); + background-color: var(--MI_THEME-accentedBg); } &:focus-visible { diff --git a/packages/frontend/src/components/MkMediaBanner.vue b/packages/frontend/src/components/MkMediaBanner.vue index 77a86ff2fb..56048a33d8 100644 --- a/packages/frontend/src/components/MkMediaBanner.vue +++ b/packages/frontend/src/components/MkMediaBanner.vue @@ -53,7 +53,7 @@ async function show() { <style lang="scss" module> .root { width: 100%; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); margin-top: 4px; overflow: clip; } @@ -68,7 +68,6 @@ async function show() { } .download { - background: var(--noteAttachedFile); } .sensitive { @@ -77,7 +76,7 @@ async function show() { } .audio { - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; } </style> diff --git a/packages/frontend/src/components/MkMediaImage.vue b/packages/frontend/src/components/MkMediaImage.vue index 02c054956c..4aca7256a5 100644 --- a/packages/frontend/src/components/MkMediaImage.vue +++ b/packages/frontend/src/components/MkMediaImage.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.indicators"> <div v-if="['image/gif', 'image/apng'].includes(image.type)" :class="$style.indicator">GIF</div> <div v-if="image.comment" :class="$style.indicator">ALT</div> - <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> + <div v-if="image.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> <div v-if="!image.comment" :class="$style.indicator" title="Image lacks descriptive text"><i class="ph-pencil-simple ph-bold ph-lg-off"></i></div> </div> <button :class="$style.menu" class="_button" @click.stop="showMenu"><i class="ti ti-dots" style="vertical-align: middle;"></i></button> @@ -166,7 +166,7 @@ function showMenu(ev: MouseEvent) { height: 100%; pointer-events: none; border-radius: inherit; - box-shadow: inset 0 0 0 4px var(--warn); + box-shadow: inset 0 0 0 4px var(--MI_THEME-warn); } } @@ -186,9 +186,9 @@ function showMenu(ev: MouseEvent) { .hide { display: block; position: absolute; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); background-color: black; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -207,28 +207,28 @@ function showMenu(ev: MouseEvent) { .visible { position: relative; - //box-shadow: 0 0 0 1px var(--divider) inset; - background: var(--bg); + //box-shadow: 0 0 0 1px var(--MI_THEME-divider) inset; + background: var(--MI_THEME-bg); background-size: 16px 16px; } html[data-color-scheme=dark] .visible { --c: rgb(255 255 255 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } html[data-color-scheme=light] .visible { --c: rgb(0 0 0 / 2%); - background-image: linear-gradient(45deg, var(--c) 16.67%, var(--bg) 16.67%, var(--bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--bg) 66.67%, var(--bg) 100%); + background-image: linear-gradient(45deg, var(--c) 16.67%, var(--MI_THEME-bg) 16.67%, var(--MI_THEME-bg) 50%, var(--c) 50%, var(--c) 66.67%, var(--MI_THEME-bg) 66.67%, var(--MI_THEME-bg) 100%); } .menu { display: block; position: absolute; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); background-color: rgba(0, 0, 0, 0.3); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); color: #fff; font-size: 0.8em; width: 28px; @@ -259,10 +259,10 @@ html[data-color-scheme=light] .visible { } .indicator { - /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; - border-radius: var(--radius-sm); - color: var(--accentLighten); + border-radius: var(--MI-radius-sm); + color: var(--MI_THEME-accentLighten); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend/src/components/MkMediaList.vue b/packages/frontend/src/components/MkMediaList.vue index 11529c186f..487cf509d6 100644 --- a/packages/frontend/src/components/MkMediaList.vue +++ b/packages/frontend/src/components/MkMediaList.vue @@ -339,19 +339,19 @@ defineExpose({ .media { overflow: hidden; // clipã«ã™ã‚‹ã¨ãƒã‚°ã‚‹ - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } :global(.pswp) { --pswp-root-z-index: var(--mk-pswp-root-z-index, 2000700) !important; - --pswp-bg: var(--modalBg) !important; + --pswp-bg: var(--MI_THEME-modalBg) !important; } </style> <style lang="scss"> .pswp__bg { - background: var(--modalBg); - backdrop-filter: var(--modalBgFilter); + background: var(--MI_THEME-modalBg); + backdrop-filter: var(--MI-modalBgFilter); } .pswp__alt-text-container { @@ -369,14 +369,14 @@ defineExpose({ } .pswp__alt-text { - color: var(--fg); + color: var(--MI_THEME-fg); margin: 0 auto; text-align: center; - padding: var(--margin); - border-radius: var(--radius); + padding: var(--MI-margin); + border-radius: var(--MI-radius); max-height: 8em; overflow-y: auto; - text-shadow: var(--bg) 0 0 10px, var(--bg) 0 0 3px, var(--bg) 0 0 3px; + text-shadow: var(--MI_THEME-bg) 0 0 10px, var(--MI_THEME-bg) 0 0 3px, var(--MI_THEME-bg) 0 0 3px; white-space: pre-line; } </style> diff --git a/packages/frontend/src/components/MkMediaRange.vue b/packages/frontend/src/components/MkMediaRange.vue index 86ed8ba2cf..df7505b0c3 100644 --- a/packages/frontend/src/components/MkMediaRange.vue +++ b/packages/frontend/src/components/MkMediaRange.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <!-- Media系専用ã®input range --> <template> -<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--scrollbarHandle);'"> +<div :style="sliderBgWhite ? '--sliderBg: rgba(255,255,255,.25);' : '--sliderBg: var(--MI_THEME-scrollbarHandle);'"> <div :class="$style.controlsSeekbar"> <progress v-if="buffer !== undefined" :class="$style.buffer" :value="isNaN(buffer) ? 0 : buffer" min="0" max="1">{{ Math.round(buffer * 100) }}% buffered</progress> <input v-model="model" :class="$style.seek" :style="`--value: ${modelValue * 100}%;`" type="range" min="0" max="1" step="any" @change="emit('dragEnded', modelValue)"/> @@ -48,7 +48,7 @@ const modelValue = computed({ background: transparent; border: 0; border-radius: 26px; - color: var(--accent); + color: var(--MI_THEME-accent); display: block; height: 19px; margin: 0; diff --git a/packages/frontend/src/components/MkMediaVideo.vue b/packages/frontend/src/components/MkMediaVideo.vue index 0502bdd401..04f8e9f8d8 100644 --- a/packages/frontend/src/components/MkMediaVideo.vue +++ b/packages/frontend/src/components/MkMediaVideo.vue @@ -42,7 +42,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i> <div :class="$style.indicators"> <div v-if="video.comment" :class="$style.indicator">ALT</div> - <div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> + <div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> </div> </div> @@ -67,7 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ti ti-eye-off" :class="$style.hide" @click="hide = true"></i> <div :class="$style.indicators"> <div v-if="video.comment" :class="$style.indicator">ALT</div> - <div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> + <div v-if="video.isSensitive" :class="$style.indicator" style="color: var(--MI_THEME-warn);" :title="i18n.ts.sensitive"><i class="ti ti-eye-exclamation"></i></div> </div> <div :class="$style.videoControls" @click.self="togglePlayPause"> <div :class="[$style.controlsChild, $style.controlsLeft]"> @@ -121,7 +121,7 @@ import { hms } from '@/filters/hms.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; -import { isFullscreenNotSupported } from '@/scripts/device-kind.js'; +import { exitFullscreen, requestFullscreen } from '@/scripts/fullscreen.js'; import hasAudio from '@/scripts/media-has-audio.js'; import MkMediaRange from '@/components/MkMediaRange.vue'; import { $i, iAmModerator } from '@/account.js'; @@ -337,26 +337,21 @@ function togglePlayPause() { } function toggleFullscreen() { - if (isFullscreenNotSupported && videoEl.value) { - if (isFullscreen.value) { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - videoEl.value.webkitExitFullscreen(); - isFullscreen.value = false; - } else { - // eslint-disable-next-line @typescript-eslint/ban-ts-comment - //@ts-ignore - videoEl.value.webkitEnterFullscreen(); - isFullscreen.value = true; - } - } else if (playerEl.value) { - if (isFullscreen.value) { - document.exitFullscreen(); - isFullscreen.value = false; - } else { - playerEl.value.requestFullscreen({ navigationUI: 'hide' }); - isFullscreen.value = true; - } + if (playerEl.value == null || videoEl.value == null) return; + if (isFullscreen.value) { + exitFullscreen({ + videoEl: videoEl.value, + }); + isFullscreen.value = false; + } else { + requestFullscreen({ + videoEl: videoEl.value, + playerEl: playerEl.value, + options: { + navigationUI: 'hide', + }, + }); + isFullscreen.value = true; } } @@ -457,8 +452,10 @@ watch(loop, (to) => { }); watch(hide, (to) => { - if (to && isFullscreen.value) { - document.exitFullscreen(); + if (videoEl.value && to && isFullscreen.value) { + exitFullscreen({ + videoEl: videoEl.value, + }); isFullscreen.value = false; } }); @@ -511,7 +508,7 @@ onDeactivated(() => { height: 100%; pointer-events: none; border-radius: inherit; - box-shadow: inset 0 0 0 4px var(--warn); + box-shadow: inset 0 0 0 4px var(--MI_THEME-warn); } } @@ -526,10 +523,10 @@ onDeactivated(() => { } .indicator { - /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; border-radius: 6px; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); display: inline-block; font-weight: bold; font-size: 0.8em; @@ -539,9 +536,9 @@ onDeactivated(() => { .hide { display: block; position: absolute; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); background-color: black; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); font-size: 12px; opacity: .5; padding: 5px 8px; @@ -595,7 +592,7 @@ onDeactivated(() => { opacity: 0; transition: opacity .4s ease-in-out; - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff; padding: 1rem; border-radius: 99rem; @@ -661,12 +658,12 @@ onDeactivated(() => { .controlButton { padding: 6px; - border-radius: calc(var(--radius) / 2); + border-radius: calc(var(--MI-radius) / 2); transition: background-color .2s ease-in-out; font-size: 1.05rem; &:hover { - background-color: var(--accent); + background-color: var(--MI_THEME-accent); } &:focus-visible { @@ -728,10 +725,10 @@ onDeactivated(() => { } .indicator { - /* Hardcode to black because either --bg or --fg makes it hard to read in dark/light mode */ + /* Hardcode to black because either --MI_THEME-bg or --MI_THEME-fg makes it hard to read in dark/light mode */ background-color: black; - border-radius: var(--radius-sm); - color: var(--accentLighten); + border-radius: var(--MI-radius-sm); + color: var(--MI_THEME-accentLighten); display: inline-block; font-weight: bold; font-size: 0.8em; diff --git a/packages/frontend/src/components/MkMention.vue b/packages/frontend/src/components/MkMention.vue index de2048b6f2..f64ca4bc77 100644 --- a/packages/frontend/src/components/MkMention.vue +++ b/packages/frontend/src/components/MkMention.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :style="{ background: bgCss }" :behavior="navigationBehavior"> +<MkA v-user-preview="canonical" :class="[$style.root, { [$style.isMe]: isMe }]" :to="url" :behavior="navigationBehavior"> <img :class="$style.icon" :src="avatarUrl" alt=""> <span> <span>@{{ username }}</span> @@ -16,7 +16,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { toUnicode } from 'punycode'; import { computed } from 'vue'; -import tinycolor from 'tinycolor2'; import { host as localHost } from '@@/js/config.js'; import { $i } from '@/account.js'; import { defaultStore } from '@/store.js'; @@ -37,11 +36,7 @@ const isMe = $i && ( `@${props.username}@${toUnicode(props.host)}` === `@${$i.username}@${toUnicode(localHost)}`.toLowerCase() ); -const bg = tinycolor(getComputedStyle(document.documentElement).getPropertyValue(isMe ? '--mentionMe' : '--mention')); -bg.setAlpha(0.1); -const bgCss = bg.toRgbString(); - -const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages +const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages || defaultStore.state.dataSaver.avatar ? getStaticImageUrl(`/avatar/@${props.username}@${props.host}`) : `/avatar/@${props.username}@${props.host}`, ); @@ -51,12 +46,14 @@ const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages .root { display: inline-block; padding: 4px 8px 4px 4px; - border-radius: var(--radius-ellipse); - color: var(--mention); + border-radius: var(--MI-radius-ellipse); + color: var(--MI_THEME-mention); + background: color(from var(--MI_THEME-mention) srgb r g b / 0.1); white-space: nowrap; &.isMe { - color: var(--mentionMe); + color: var(--MI_THEME-mentionMe); + background: color(from var(--MI_THEME-mentionMe) srgb r g b / 0.1); } } @@ -66,7 +63,7 @@ const avatarUrl = computed(() => defaultStore.state.disableShowingAnimatedImages object-fit: cover; margin: 0 0.2em 0 0; vertical-align: bottom; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .host { diff --git a/packages/frontend/src/components/MkMenu.vue b/packages/frontend/src/components/MkMenu.vue index fe6df7090c..b0a1b80210 100644 --- a/packages/frontend/src/components/MkMenu.vue +++ b/packages/frontend/src/components/MkMenu.vue @@ -49,7 +49,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> <div :class="$style.item_content"> <span :class="$style.item_content_text">{{ item.text }}</span> - <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> + <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span> </div> </MkA> <a @@ -68,7 +68,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-if="item.icon" class="ti-fw" :class="[$style.icon, item.icon]"></i> <div :class="$style.item_content"> <span :class="$style.item_content_text">{{ item.text }}</span> - <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> + <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span> </div> </a> <button @@ -82,7 +82,7 @@ SPDX-License-Identifier: AGPL-3.0-only > <MkAvatar :user="item.user" :class="$style.avatar"/><MkUserName :user="item.user"/> <div v-if="item.indicate" :class="$style.item_content"> - <span :class="$style.indicator"><i class="_indicatorCircle"></i></span> + <span :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span> </div> </button> <button @@ -161,7 +161,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAvatar v-if="item.avatar" :user="item.avatar" :class="$style.avatar"/> <div :class="$style.item_content"> <span :class="$style.item_content_text">{{ item.text }}</span> - <span v-if="item.indicate" :class="$style.indicator"><i class="_indicatorCircle"></i></span> + <span v-if="item.indicate" :class="$style.indicator" class="_blink"><i class="_indicatorCircle"></i></span> </div> </button> </template> @@ -437,9 +437,11 @@ onBeforeUnmount(() => { &.big:not(.asDrawer) { > .menu { + min-width: 230px; + > .item { padding: 6px 20px; - font-size: 1em; + font-size: 0.95em; line-height: 24px; } } @@ -452,7 +454,7 @@ onBeforeUnmount(() => { > .menu { padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; width: 100%; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); border-bottom-right-radius: 0; border-bottom-left-radius: 0; @@ -462,7 +464,7 @@ onBeforeUnmount(() => { &::before { width: calc(100% - 24px); - border-radius: var(--radius); + border-radius: var(--MI-radius); } > .icon { @@ -505,7 +507,7 @@ onBeforeUnmount(() => { overflow: hidden; text-overflow: ellipsis; text-decoration: none !important; - color: var(--menuFg, var(--fg)); + color: var(--menuFg, var(--MI_THEME-fg)); &::before { content: ""; @@ -518,14 +520,14 @@ onBeforeUnmount(() => { margin: auto; width: calc(100% - 16px); height: 100%; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } &:focus-visible { outline: none; &:not(:hover):not(:active)::before { - outline: var(--focus) solid 2px; + outline: var(--MI_THEME-focus) solid 2px; outline-offset: -2px; } } @@ -534,19 +536,19 @@ onBeforeUnmount(() => { &:hover, &:focus-visible:active, &:focus-visible.active { - color: var(--menuHoverFg, var(--accent)); + color: var(--menuHoverFg, var(--MI_THEME-accent)); &::before { - background-color: var(--menuHoverBg, var(--accentedBg)); + background-color: var(--menuHoverBg, var(--MI_THEME-accentedBg)); } } &:not(:focus-visible):active, &:not(:focus-visible).active { - color: var(--menuActiveFg, var(--fgOnAccent)); + color: var(--menuActiveFg, var(--MI_THEME-fgOnAccent)); &::before { - background-color: var(--menuActiveBg, var(--accent)); + background-color: var(--menuActiveBg, var(--MI_THEME-accent)); } } } @@ -564,13 +566,13 @@ onBeforeUnmount(() => { } &.radio { - --menuActiveFg: var(--accent); - --menuActiveBg: var(--accentedBg); + --menuActiveFg: var(--MI_THEME-accent); + --menuActiveBg: var(--MI_THEME-accentedBg); } &.parent { - --menuActiveFg: var(--accent); - --menuActiveBg: var(--accentedBg); + --menuActiveFg: var(--MI_THEME-accent); + --menuActiveBg: var(--MI_THEME-accentedBg); } &.label { @@ -635,14 +637,13 @@ onBeforeUnmount(() => { .indicator { display: flex; align-items: center; - color: var(--indicator); + color: var(--MI_THEME-indicator); font-size: 12px; - animation: global-blink 1s infinite; } .divider { margin: 8px 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .radioIcon { @@ -652,11 +653,11 @@ onBeforeUnmount(() => { height: 1em; vertical-align: -0.125em; border-radius: 50%; - border: solid 2px var(--divider); - background-color: var(--panel); + border: solid 2px var(--MI_THEME-divider); + background-color: var(--MI_THEME-panel); &.radioChecked { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); &::after { content: ""; @@ -668,7 +669,7 @@ onBeforeUnmount(() => { width: 50%; height: 50%; border-radius: 50%; - background-color: var(--accent); + background-color: var(--MI_THEME-accent); } } } diff --git a/packages/frontend/src/components/MkMiniChart.vue b/packages/frontend/src/components/MkMiniChart.vue index 1b6f6cef31..7ea585ecc2 100644 --- a/packages/frontend/src/components/MkMiniChart.vue +++ b/packages/frontend/src/components/MkMiniChart.vue @@ -48,7 +48,7 @@ const polygonPoints = ref(''); const headX = ref<number | null>(null); const headY = ref<number | null>(null); const clock = ref<number | null>(null); -const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent')); +const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); const color = accent.toRgbString(); function draw(): void { diff --git a/packages/frontend/src/components/MkModalWindow.vue b/packages/frontend/src/components/MkModalWindow.vue index f26959888b..f06cfffee4 100644 --- a/packages/frontend/src/components/MkModalWindow.vue +++ b/packages/frontend/src/components/MkModalWindow.vue @@ -26,11 +26,11 @@ import { onMounted, onUnmounted, shallowRef, ref } from 'vue'; import MkModal from './MkModal.vue'; const props = withDefaults(defineProps<{ - withOkButton: boolean; - withCloseButton: boolean; - okButtonDisabled: boolean; - width: number; - height: number; + withOkButton?: boolean; + withCloseButton?: boolean; + okButtonDisabled?: boolean; + width?: number; + height?: number; }>(), { withOkButton: false, withCloseButton: true, @@ -90,12 +90,12 @@ defineExpose({ display: flex; flex-direction: column; contain: content; - border-radius: var(--radius); + border-radius: var(--MI-radius); --root-margin: 24px; - --headerHeight: 46px; - --headerHeightNarrow: 42px; + --MI_THEME-headerHeight: 46px; + --MI_THEME-headerHeightNarrow: 42px; @media (max-width: 500px) { --root-margin: 16px; @@ -105,24 +105,24 @@ defineExpose({ .header { display: flex; flex-shrink: 0; - background: var(--windowHeader); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-windowHeader); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .headerButton { - height: var(--headerHeight); - width: var(--headerHeight); + height: var(--MI_THEME-headerHeight); + width: var(--MI_THEME-headerHeight); @media (max-width: 500px) { - height: var(--headerHeightNarrow); - width: var(--headerHeightNarrow); + height: var(--MI_THEME-headerHeightNarrow); + width: var(--MI_THEME-headerHeightNarrow); } } .title { flex: 1; - line-height: var(--headerHeight); + line-height: var(--MI_THEME-headerHeight); padding-left: 32px; font-weight: bold; white-space: nowrap; @@ -131,7 +131,7 @@ defineExpose({ pointer-events: none; @media (max-width: 500px) { - line-height: var(--headerHeightNarrow); + line-height: var(--MI_THEME-headerHeightNarrow); padding-left: 16px; } } @@ -143,7 +143,7 @@ defineExpose({ .body { flex: 1; overflow: auto; - background: var(--panel); + background: var(--MI_THEME-panel); container-type: size; } </style> diff --git a/packages/frontend/src/components/MkNote.vue b/packages/frontend/src/components/MkNote.vue index 323f6e994e..39f4806f0c 100644 --- a/packages/frontend/src/components/MkNote.vue +++ b/packages/frontend/src/components/MkNote.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-show="!isDeleted" ref="rootEl" v-hotkey="keymap" - :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" + :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]" :tabindex="isDeleted ? '-1' : '0'" > <div v-if="appearNote.reply && inReplyToCollapsed" :class="$style.collapsedInReplyTo"> @@ -132,7 +132,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" :class="$style.footerButton" class="_button" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @click.stop @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility()" > @@ -156,8 +156,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ph-heart ph-bold ph-lg"></i> </button> <button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()" @click.stop> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> - <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i> + <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p> @@ -201,6 +201,9 @@ import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } fro import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { host } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteHeader from '@/components/MkNoteHeader.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; @@ -215,6 +218,7 @@ import MkInstanceTicker from '@/components/MkInstanceTicker.vue'; import MkButton from '@/components/MkButton.vue'; import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; +import { notePage } from '@/filters/note.js'; import { userPage } from '@/filters/user.js'; import number from '@/filters/number.js'; import * as os from '@/os.js'; @@ -233,13 +237,10 @@ import { deepClone } from '@/scripts/clone.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteSummary } from '@/scripts/get-note-summary.js'; -import type { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { useRouter } from '@/router/supplier.js'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; -import { shouldCollapsed } from '@@/js/collapsed.js'; -import { host } from '@@/js/config.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { type Keymap } from '@/scripts/hotkey.js'; import { focusPrev, focusNext } from '@/scripts/focus.js'; @@ -264,6 +265,7 @@ const emit = defineEmits<{ const router = useRouter(); const inTimeline = inject<boolean>('inTimeline', false); +const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true)); const inChannel = inject('inChannel', null); const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null); @@ -343,15 +345,18 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute'; */ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' { - if (mutedWords == null) return false; - - if (checkWordMute(noteToCheck, $i, mutedWords)) return true; - if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true; - if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true; + if (mutedWords != null) { + if (checkWordMute(noteToCheck, $i, mutedWords)) return true; + if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true; + if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true; + } if (checkOnly) return false; - if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute'; + if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) { + return 'sensitiveMute'; + } + return false; } @@ -514,7 +519,7 @@ function boostVisibility() { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); renoting = true; @@ -564,7 +569,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (props.mock) { return; @@ -625,7 +630,7 @@ function quote() { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (props.mock) { return; } @@ -638,7 +643,7 @@ function reply(): void { } function like(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); if (props.mock) { @@ -660,7 +665,7 @@ function like(): void { } function react(viaKeyboard = false): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -808,15 +813,24 @@ function showRenoteMenu(): void { }; } + const renoteDetailsMenu: MenuItem = { + type: 'link', + text: i18n.ts.renoteDetails, + icon: 'ti ti-info-circle', + to: notePage(note.value), + }; + if (isMyRenote) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([ + renoteDetailsMenu, getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), { type: 'divider' }, getUnrenote(), ], renoteTime.value); } else { os.popupMenu([ + renoteDetailsMenu, getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), { type: 'divider' }, getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), @@ -877,14 +891,6 @@ function emitUpdReaction(emoji: string, delta: number) { overflow: clip; contain: content; - // ã“ã‚Œã‚‰ã®æŒ‡å®šã¯ãƒ‘フォーマンスå‘上ã«ã¯æœ‰åйã ãŒã€ãƒŽãƒ¼ãƒˆã®é«˜ã•ã¯ä¸€å®šã§ãªã„ãŸã‚〠- // ä¸‹ã®æ–¹ã¾ã§ã‚¹ã‚¯ãƒãƒ¼ãƒ«ã™ã‚‹ã¨ä¸Šã®ãƒŽãƒ¼ãƒˆã®é«˜ã•ãŒã“ã“ã§æ±ºã‚打ã¡ã•れãŸã‚‚ã®ã«å¤‰åŒ–ã—ã€è¡¨ç¤ºã—ã¦ã„るノートã®ä½ç½®ãŒå¤‰ã‚ã£ã¦ã—ã¾ã† - // ノートãŒãƒžã‚¦ãƒ³ãƒˆã•れãŸã¨ãã«è‡ªèº«ã®é«˜ã•ã‚’å–å¾—ã— contain-intrinsic-size ã‚’è¨å®šã—ãªãŠã›ã°ã»ã¼è§£æ±ºã§ããã†ã ãŒã€ - // 今度ã¯ãã®å‡¦ç†è‡ªä½“ãŒãƒ‘フォーマンス低下ã®åŽŸå› ã«ãªã‚‰ãªã„ã‹æ‡¸å¿µã•れる。ã¾ãŸã€è¢«ãƒªã‚¢ã‚¯ã‚·ãƒ§ãƒ³ã§ã‚‚高ã•ã¯å¤‰åŒ–ã™ã‚‹ãŸã‚ã€ã‚„ã¯ã‚Šå¤šå°‘ã®ã‚ºãƒ¬ã¯ç”Ÿã˜ã‚‹ - // 一度レンダリングã•れãŸè¦ç´ ã¯ãƒ–ラウザãŒã‚ˆã—ãªã«ã‚µã‚¤ã‚ºã‚’覚ãˆã¦ãŠã„ã¦ãれるよã†ãªå®Ÿè£…ã«ãªã‚‹ã¾ã§å¾…ã£ãŸæ–¹ãŒè‰¯ã•ãã†(ãªã‚‹ã®ã‹ï¼Ÿ) - //content-visibility: auto; - //contain-intrinsic-size: 0 128px; - &:focus-visible { outline: none; @@ -901,8 +907,8 @@ function emitUpdReaction(emoji: string, delta: number) { margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: dashed 2px var(--focus); - border-radius: var(--radius); + border: dashed 2px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -929,9 +935,9 @@ function emitUpdReaction(emoji: string, delta: number) { right: 12px; padding: 0 4px; margin-bottom: 0 !important; - background: var(--popup); - border-radius: var(--radius-sm); - box-shadow: 0px 4px 32px var(--shadow); + background: var(--MI_THEME-popup); + border-radius: var(--MI-radius-sm); + box-shadow: 0px 4px 32px var(--MI_THEME-shadow); } .footerButton { @@ -950,6 +956,11 @@ function emitUpdReaction(emoji: string, delta: number) { } } +.skipRender { + content-visibility: auto; + contain-intrinsic-size: 0 150px; +} + .tip { display: flex; align-items: center; @@ -976,7 +987,7 @@ function emitUpdReaction(emoji: string, delta: number) { padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); & + .article { padding-top: 8px; @@ -1070,7 +1081,7 @@ function emitUpdReaction(emoji: string, delta: number) { left: 8px; width: 5px; height: calc(100% - 16px); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); pointer-events: none; } @@ -1078,10 +1089,10 @@ function emitUpdReaction(emoji: string, delta: number) { flex-shrink: 0; display: block !important; margin: 0 14px 0 0; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); position: sticky !important; - top: calc(22px + var(--stickyTop, 0px)); + top: calc(22px + var(--MI-stickyTop, 0px)); left: 0; } @@ -1101,15 +1112,15 @@ function emitUpdReaction(emoji: string, delta: number) { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) - 100px); + bottom: calc(var(--MI-stickyBottom, 0px) - 100px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } @@ -1127,19 +1138,19 @@ function emitUpdReaction(emoji: string, delta: number) { z-index: 2; width: 100%; height: 64px; - //background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + //background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); &:hover > .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } @@ -1149,13 +1160,13 @@ function emitUpdReaction(emoji: string, delta: number) { } .replyIcon { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -1178,8 +1189,8 @@ function emitUpdReaction(emoji: string, delta: number) { .quoteNote { padding: 16px; - border: dashed 1px var(--renote); - border-radius: var(--radius-sm); + border: dashed 1px var(--MI_THEME-renote); + border-radius: var(--MI-radius-sm); overflow: clip; } @@ -1202,7 +1213,7 @@ function emitUpdReaction(emoji: string, delta: number) { } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -1277,7 +1288,7 @@ function emitUpdReaction(emoji: string, delta: number) { margin: 0 10px 0 0; width: 46px; height: 46px; - top: calc(14px + var(--stickyTop, 0px)); + top: calc(14px + var(--MI-stickyTop, 0px)); } } diff --git a/packages/frontend/src/components/MkNoteDetailed.vue b/packages/frontend/src/components/MkNoteDetailed.vue index 5acb18c871..a537d8afb9 100644 --- a/packages/frontend/src/components/MkNoteDetailed.vue +++ b/packages/frontend/src/components/MkNoteDetailed.vue @@ -63,7 +63,14 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="appearNote.localOnly" style="margin-left: 0.5em;" :title="i18n.ts._visibility['disableFederation']"><i class="ti ti-rocket-off"></i></span> </div> </div> - <div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div> + <div :class="$style.noteHeaderUsernameAndBadgeRoles"> + <div :class="$style.noteHeaderUsername"> + <MkAcct :user="appearNote.user"/> + </div> + <div v-if="appearNote.user.badgeRoles" :class="$style.noteHeaderBadgeRoles"> + <img v-for="(role, i) in appearNote.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.noteHeaderBadgeRole" :src="role.iconUrl!"/> + </div> + </div> <MkInstanceTicker v-if="showTicker" :instance="appearNote.user.instance"/> </div> </header> @@ -135,7 +142,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" class="_button" :class="$style.noteFooterButton" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @mousedown.prevent="renoted ? undoRenote() : boostVisibility()" > <i class="ti ti-repeat"></i> @@ -157,8 +164,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ph-heart ph-bold ph-lg"></i> </button> <button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()"> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> - <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i> + <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p> @@ -236,6 +243,7 @@ import { computed, inject, onMounted, provide, ref, shallowRef, watch } from 'vu import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; +import { host } from '@@/js/config.js'; import MkNoteSub from '@/components/MkNoteSub.vue'; import MkNoteSimple from '@/components/MkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -259,10 +267,8 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; -import { host } from '@@/js/config.js'; import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js'; import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js'; - import { useNoteCapture } from '@/scripts/use-note-capture.js'; import { deepClone } from '@/scripts/clone.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; @@ -507,7 +513,7 @@ if (appearNote.value.reactionAcceptance === 'likeOnly') { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); renoting = true; @@ -553,7 +559,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -611,7 +617,7 @@ function quote() { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); os.post({ reply: appearNote.value, @@ -622,7 +628,7 @@ function reply(): void { } function react(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -659,7 +665,7 @@ function react(): void { } function like(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); misskeyApi('notes/like', { @@ -741,7 +747,7 @@ async function clip(): Promise<void> { function showRenoteMenu(): void { if (!isMyRenote) return; - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([{ text: i18n.ts.unrenote, icon: 'ti ti-trash', @@ -843,8 +849,8 @@ function animatedMFM() { margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: dashed 2px var(--focus); - border-radius: var(--radius); + border: dashed 2px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -874,7 +880,7 @@ function animatedMFM() { padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); } .renoteAvatar { @@ -883,7 +889,7 @@ function animatedMFM() { width: 28px; height: 28px; margin: 0 8px 0 0; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .renoteText { @@ -932,8 +938,8 @@ function animatedMFM() { .noteHeaderAvatar { display: block; flex-shrink: 0; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); } .noteHeaderBody { @@ -956,16 +962,21 @@ function animatedMFM() { padding: 4px 6px; font-size: 80%; line-height: 1; - border: solid 0.5px var(--divider); - border-radius: var(--radius-xs); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs); } .noteHeaderInfo { float: right; } +.noteHeaderUsernameAndBadgeRoles { + display: flex; +} + .noteHeaderUsername { margin-bottom: 2px; + margin-right: 0.5em; line-height: 1.3; word-wrap: anywhere; } @@ -974,6 +985,19 @@ function animatedMFM() { margin-top: 5px; } +.noteHeaderBadgeRoles { + margin: 0 .5em 0 0; +} + +.noteHeaderBadgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .noteHeaderBadgeRole { + margin-left: 0.2em; + } +} + .noteContent { container-type: inline-size; overflow-wrap: break-word; @@ -989,19 +1013,19 @@ function animatedMFM() { } .noteReplyTarget { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .rn { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -1016,8 +1040,8 @@ function animatedMFM() { .quoteNote { padding: 16px; - border: dashed 1px var(--renote); - border-radius: var(--radius-sm); + border: dashed 1px var(--MI_THEME-renote); + border-radius: var(--MI-radius-sm); overflow: clip; } @@ -1042,7 +1066,7 @@ function animatedMFM() { } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -1052,17 +1076,17 @@ function animatedMFM() { opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } .reply:not(:first-child) { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .tabs { - border-top: solid 0.5px var(--divider); - border-bottom: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); display: flex; } @@ -1074,7 +1098,7 @@ function animatedMFM() { } .tabActive { - border-bottom: solid 2px var(--accent); + border-bottom: solid 2px var(--MI_THEME-accent); } .tab_renotes { @@ -1094,12 +1118,12 @@ function animatedMFM() { .reactionTab { padding: 4px 6px; - border: solid 1px var(--divider); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); } .reactionTabActive { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } @container (max-width: 500px) { diff --git a/packages/frontend/src/components/MkNoteHeader.vue b/packages/frontend/src/components/MkNoteHeader.vue index 2c69048ec5..3b15242685 100644 --- a/packages/frontend/src/components/MkNoteHeader.vue +++ b/packages/frontend/src/components/MkNoteHeader.vue @@ -5,18 +5,14 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <header :class="$style.root"> - <component :is="defaultStore.state.enableCondensedLine ? 'MkCondensedLine' : 'div'" :minScale="0.5" style="min-width: 0;"> - <div style="display: flex; white-space: nowrap; align-items: baseline;"> - <div v-if="mock" :class="$style.name"> - <MkUserName :user="note.user"/> - </div> - <MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> - <MkUserName :user="note.user"/> - </MkA> - <div v-if="note.user.isBot" :class="$style.isBot">bot</div> - <div :class="$style.username"><MkAcct :user="note.user"/></div> - </div> - </component> + <div v-if="mock" :class="$style.name"> + <MkUserName :user="note.user"/> + </div> + <MkA v-else v-user-preview="note.user.id" :class="$style.name" :to="userPage(note.user)"> + <MkUserName :user="note.user"/> + </MkA> + <div v-if="note.user.isBot" :class="$style.isBot">bot</div> + <div :class="$style.username"><MkAcct :user="note.user"/></div> <div v-if="note.user.badgeRoles" :class="$style.badgeRoles"> <img v-for="(role, i) in note.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl!"/> </div> @@ -98,8 +94,8 @@ const mock = inject<boolean>('mock', false); margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 0.5px var(--divider); - border-radius: var(--radius-xs); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs); } .username { diff --git a/packages/frontend/src/components/MkNotePreview.vue b/packages/frontend/src/components/MkNotePreview.vue index 7ccc2c0320..4f907231bb 100644 --- a/packages/frontend/src/components/MkNotePreview.vue +++ b/packages/frontend/src/components/MkNotePreview.vue @@ -56,7 +56,7 @@ const props = defineProps<{ margin: 0 10px 0 0 !important; width: 40px !important; height: 40px !important; - border-radius: var(--radius-sm) !important; + border-radius: var(--MI-radius-sm) !important; pointer-events: none !important; } diff --git a/packages/frontend/src/components/MkNoteSimple.vue b/packages/frontend/src/components/MkNoteSimple.vue index a369d84783..9ef45a0ec3 100644 --- a/packages/frontend/src/components/MkNoteSimple.vue +++ b/packages/frontend/src/components/MkNoteSimple.vue @@ -95,8 +95,8 @@ watch(() => props.expandAllCws, (expandAllCws) => { } .button{ - margin-right: var(--margin); - margin-bottom: var(--margin); + margin-right: var(--MI-margin); + margin-bottom: var(--MI-margin); } .avatar { @@ -105,9 +105,9 @@ watch(() => props.expandAllCws, (expandAllCws) => { margin: 0 10px 0 0; width: 34px; height: 34px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); position: sticky !important; - top: calc(16px + var(--stickyTop, 0px)); + top: calc(16px + var(--MI-stickyTop, 0px)); left: 0; } diff --git a/packages/frontend/src/components/MkNoteSub.vue b/packages/frontend/src/components/MkNoteSub.vue index 45276839ad..3523babe46 100644 --- a/packages/frontend/src/components/MkNoteSub.vue +++ b/packages/frontend/src/components/MkNoteSub.vue @@ -30,7 +30,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" class="_button" :class="$style.noteFooterButton" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @mousedown="renoted ? undoRenote() : boostVisibility()" > <i class="ph-rocket-launch ph-bold ph-lg"></i> @@ -98,7 +98,8 @@ import { $i } from '@/account.js'; import { userPage } from '@/filters/user.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; import { defaultStore } from '@/store.js'; -import { pleaseLogin } from '@/scripts/please-login.js'; +import { host } from '@@/js/config.js'; +import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -145,6 +146,11 @@ const isRenote = ( props.note.poll == null ); +const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ + type: 'lookup', + url: `https://${host}/notes/${appearNote.value.id}`, +})); + async function addReplyTo(replyNote: Misskey.entities.Note) { replies.value.unshift(replyNote); appearNote.value.repliesCount += 1; @@ -182,7 +188,7 @@ function focus() { } function reply(viaKeyboard = false): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); os.post({ reply: props.note, @@ -194,7 +200,7 @@ function reply(viaKeyboard = false): void { } function react(viaKeyboard = false): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); if (props.note.reactionAcceptance === 'likeOnly') { @@ -228,7 +234,7 @@ function react(viaKeyboard = false): void { } function like(): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); misskeyApi('notes/like', { @@ -288,7 +294,7 @@ function boostVisibility() { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -332,7 +338,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -438,7 +444,7 @@ if (props.detail) { left: 8px; width: 5px; height: calc(100% - 8px); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); pointer-events: none; } @@ -448,7 +454,7 @@ if (props.detail) { margin: 0 8px 0 0; width: 38px; height: 38px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .body { @@ -475,7 +481,7 @@ if (props.detail) { } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -493,7 +499,7 @@ if (props.detail) { opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -510,7 +516,7 @@ if (props.detail) { } .reply, .more { - border-left: solid 0.5px var(--divider); + border-left: solid 0.5px var(--MI_THEME-divider); margin-top: 10px; } @@ -531,8 +537,8 @@ if (props.detail) { .muted { text-align: center; padding: 8px !important; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); margin: 8px 8px 0 8px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } </style> diff --git a/packages/frontend/src/components/MkNotes.vue b/packages/frontend/src/components/MkNotes.vue index 15173fbd99..b13df2813b 100644 --- a/packages/frontend/src/components/MkNotes.vue +++ b/packages/frontend/src/components/MkNotes.vue @@ -61,20 +61,20 @@ defineExpose({ <style lang="scss" module> .root { &.noGap { - border-radius: var(--radius); + border-radius: var(--MI-radius); > .notes { - background: color-mix(in srgb, var(--panel) 65%, transparent); + background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); } } &:not(.noGap) { > .notes { - background: var(--bg); + background: var(--MI_THEME-bg); .note { - background: color-mix(in srgb, var(--panel) 65%, transparent); - border-radius: var(--radius); + background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); + border-radius: var(--MI-radius); } } } diff --git a/packages/frontend/src/components/MkNotification.vue b/packages/frontend/src/components/MkNotification.vue index f713d46137..4620b966af 100644 --- a/packages/frontend/src/components/MkNotification.vue +++ b/packages/frontend/src/components/MkNotification.vue @@ -7,13 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.root"> <div :class="$style.head"> <MkAvatar v-if="['pollEnded', 'note', 'edited', 'scheduledNotePosted'].includes(notification.type) && 'note' in notification" :class="$style.icon" :user="notification.note.user" link preview/> - <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> - <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> + <MkAvatar v-else-if="['roleAssigned', 'achievementEarned', 'exportCompleted', 'login', 'scheduledNoteFailed'].includes(notification.type)" :class="$style.icon" :user="$i" link preview/> + <div v-else-if="notification.type === 'reaction:grouped' && notification.note.reactionAcceptance === 'likeOnly'" :class="[$style.icon, $style.icon_reactionGroupHeart]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'reaction:grouped'" :class="[$style.icon, $style.icon_reactionGroup]"><i class="ph-smiley ph-bold ph-lg" style="line-height: 1;"></i></div> <div v-else-if="notification.type === 'renote:grouped'" :class="[$style.icon, $style.icon_renoteGroup]"><i class="ti ti-repeat" style="line-height: 1;"></i></div> <img v-else-if="notification.type === 'test'" :class="$style.icon" :src="infoImageUrl"/> <MkAvatar v-else-if="'user' in notification" :class="$style.icon" :user="notification.user" link preview/> - <MkAvatar v-else-if="notification.type === 'exportCompleted'" :class="$style.icon" :user="$i" link preview/> <img v-else-if="'icon' in notification && notification.icon != null" :class="[$style.icon, $style.icon_app]" :src="notification.icon" alt=""/> <div :class="[$style.subIcon, { @@ -27,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only [$style.t_pollEnded]: notification.type === 'pollEnded', [$style.t_achievementEarned]: notification.type === 'achievementEarned', [$style.t_exportCompleted]: notification.type === 'exportCompleted', + [$style.t_login]: notification.type === 'login', [$style.t_roleAssigned]: notification.type === 'roleAssigned' && notification.role.iconUrl == null, [$style.t_pollEnded]: notification.type === 'edited', [$style.t_roleAssigned]: notification.type === 'scheduledNoteFailed', @@ -43,6 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only <i v-else-if="notification.type === 'pollEnded'" class="ti ti-chart-arrows"></i> <i v-else-if="notification.type === 'achievementEarned'" class="ti ti-medal"></i> <i v-else-if="notification.type === 'exportCompleted'" class="ti ti-archive"></i> + <i v-else-if="notification.type === 'login'" class="ti ti-login-2"></i> <template v-else-if="notification.type === 'roleAssigned'"> <img v-if="notification.role.iconUrl" style="height: 1.3em; vertical-align: -22%;" :src="notification.role.iconUrl" alt=""/> <i v-else class="ti ti-badges"></i> @@ -66,6 +67,7 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-else-if="notification.type === 'note'">{{ i18n.ts._notification.newNote }}: <MkUserName :user="notification.note.user"/></span> <span v-else-if="notification.type === 'roleAssigned'">{{ i18n.ts._notification.roleAssigned }}</span> <span v-else-if="notification.type === 'achievementEarned'">{{ i18n.ts._notification.achievementEarned }}</span> + <span v-else-if="notification.type === 'login'">{{ i18n.ts._notification.login }}</span> <span v-else-if="notification.type === 'test'">{{ i18n.ts._notification.testNotification }}</span> <span v-else-if="notification.type === 'exportCompleted'">{{ i18n.tsx._notification.exportOfXCompleted({ x: exportEntityName[notification.exportedEntity] }) }}</span> <MkA v-else-if="notification.type === 'follow' || notification.type === 'mention' || notification.type === 'reply' || notification.type === 'renote' || notification.type === 'quote' || notification.type === 'reaction' || notification.type === 'receiveFollowRequest' || notification.type === 'followRequestAccepted'" v-user-preview="notification.user.id" :class="$style.headerName" :to="userPage(notification.user)"><MkUserName :user="notification.user"/></MkA> @@ -243,13 +245,16 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) overflow-wrap: break-word; display: flex; contain: content; + content-visibility: auto; + contain-intrinsic-size: 0 100px; --eventFollow: #36aed2; --eventRenote: #36d298; --eventReply: #007aff; - --eventReactionHeart: var(--love); + --eventReactionHeart: var(--MI_THEME-love); --eventReaction: #e99a0b; --eventAchievement: #cb9a11; + --eventLogin: #007aff; --eventOther: #88a6b7; } @@ -277,7 +282,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) width: 80%; height: 80%; font-size: 15px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); color: #fff; } @@ -294,7 +299,7 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) } .icon_app { - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .subIcon { @@ -305,9 +310,9 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) width: 20px; height: 20px; box-sizing: border-box; - border-radius: var(--radius-full); - background: var(--panel); - box-shadow: 0 0 0 3px var(--panel); + border-radius: var(--MI-radius-full); + background: var(--MI_THEME-panel); + box-shadow: 0 0 0 3px var(--MI_THEME-panel); font-size: 11px; text-align: center; color: #fff; @@ -371,6 +376,12 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) pointer-events: none; } +.t_login { + padding: 3px; + background: var(--eventLogin); + pointer-events: none; +} + .tail { flex: 1; min-width: 0; @@ -452,9 +463,9 @@ function getActualReactedUsersCount(notification: Misskey.entities.Notification) width: 20px; height: 20px; box-sizing: border-box; - border-radius: var(--radius-full); - background: var(--panel); - box-shadow: 0 0 0 3px var(--panel); + border-radius: var(--MI-radius-full); + background: var(--MI_THEME-panel); + box-shadow: 0 0 0 3px var(--MI_THEME-panel); font-size: 11px; text-align: center; color: #fff; diff --git a/packages/frontend/src/components/MkNotificationSelectWindow.vue b/packages/frontend/src/components/MkNotificationSelectWindow.vue index 47a9c79e45..d07827d11a 100644 --- a/packages/frontend/src/components/MkNotificationSelectWindow.vue +++ b/packages/frontend/src/components/MkNotificationSelectWindow.vue @@ -53,7 +53,7 @@ const props = withDefaults(defineProps<{ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); -const typesMap: TypesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as any); +const typesMap = notificationTypes.reduce((p, t) => ({ ...p, [t]: ref<boolean>(!props.excludeTypes.includes(t)) }), {} as TypesMap); function ok() { emit('done', { diff --git a/packages/frontend/src/components/MkNotifications.vue b/packages/frontend/src/components/MkNotifications.vue index a395734add..51c4ea7ce4 100644 --- a/packages/frontend/src/components/MkNotifications.vue +++ b/packages/frontend/src/components/MkNotifications.vue @@ -111,6 +111,6 @@ defineExpose({ <style lang="scss" module> .list { - background: var(--panel); + background: var(--MI_THEME-panel); } </style> diff --git a/packages/frontend/src/components/MkNumberDiff.vue b/packages/frontend/src/components/MkNumberDiff.vue index 1825cc5405..80c634fdce 100644 --- a/packages/frontend/src/components/MkNumberDiff.vue +++ b/packages/frontend/src/components/MkNumberDiff.vue @@ -24,11 +24,11 @@ const isZero = computed(() => props.value === 0); <style lang="scss" module> .isPlus { - color: var(--success); + color: var(--MI_THEME-success); } .isMinus { - color: var(--error); + color: var(--MI_THEME-error); } .isZero { diff --git a/packages/frontend/src/components/MkObjectView.value.vue b/packages/frontend/src/components/MkObjectView.value.vue index 870599aa94..7fa8c23c6c 100644 --- a/packages/frontend/src/components/MkObjectView.value.vue +++ b/packages/frontend/src/components/MkObjectView.value.vue @@ -39,7 +39,7 @@ import number from '@/filters/number.js'; import XValue from '@/components/MkObjectView.value.vue'; const props = defineProps<{ - value: any; + value: unknown; }>(); const collapsed = reactive({}); @@ -50,19 +50,19 @@ if (isObject(props.value)) { } } -function isObject(v): boolean { +function isObject(v: unknown): v is Record<PropertyKey, unknown> { return typeof v === 'object' && !Array.isArray(v) && v !== null; } -function isArray(v): boolean { +function isArray(v: unknown): v is unknown[] { return Array.isArray(v); } -function isEmpty(v): boolean { +function isEmpty(v: unknown): v is Record<PropertyKey, never> | never[] { return (isArray(v) && v.length === 0) || (isObject(v) && Object.keys(v).length === 0); } -function collapsable(v): boolean { +function collapsable(v: unknown): boolean { return (isObject(v) || isArray(v)) && !isEmpty(v); } </script> @@ -78,7 +78,7 @@ function collapsable(v): boolean { > .boolean { display: inline; - color: var(--codeBoolean); + color: var(--MI_THEME-codeBoolean); &.true { font-weight: bold; @@ -91,12 +91,12 @@ function collapsable(v): boolean { > .string { display: inline; - color: var(--codeString); + color: var(--MI_THEME-codeString); } > .number { display: inline; - color: var(--codeNumber); + color: var(--MI_THEME-codeNumber); } > .array.empty { @@ -127,7 +127,7 @@ function collapsable(v): boolean { > .toggle { width: 16px; - color: var(--accent); + color: var(--MI_THEME-accent); visibility: hidden; &.visible { diff --git a/packages/frontend/src/components/MkOmit.vue b/packages/frontend/src/components/MkOmit.vue index 94cbaf5c91..b978a71c15 100644 --- a/packages/frontend/src/components/MkOmit.vue +++ b/packages/frontend/src/components/MkOmit.vue @@ -47,7 +47,7 @@ onUnmounted(() => { <style lang="scss" module> .content { - --stickyTop: 0px; + --MI-stickyTop: 0px; &.omitted { position: relative; @@ -62,20 +62,20 @@ onUnmounted(() => { left: 0; width: 100%; height: 64px; - //background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + //background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } diff --git a/packages/frontend/src/components/MkPagePreview.vue b/packages/frontend/src/components/MkPagePreview.vue index 361cc520de..29fdc91a2b 100644 --- a/packages/frontend/src/components/MkPagePreview.vue +++ b/packages/frontend/src/components/MkPagePreview.vue @@ -41,7 +41,7 @@ const props = defineProps<{ .eyeCatchingImageRoot { width: 100%; height: 200px; - border-radius: var(--radius) var(--radius) 0 0; + border-radius: var(--MI-radius) var(--MI-radius) 0 0; overflow: hidden; } </style> @@ -53,7 +53,7 @@ const props = defineProps<{ &:hover { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); } &:focus-within { @@ -66,22 +66,22 @@ const props = defineProps<{ left: 0; width: 100%; height: 100%; - border-radius: var(--radius); + border-radius: var(--MI-radius); pointer-events: none; - box-shadow: inset 0 0 0 2px var(--focus); + box-shadow: inset 0 0 0 2px var(--MI_THEME-focus); } } > .thumbnail { & + article { - border-radius: 0 0 var(--radius) var(--radius); + border-radius: 0 0 var(--MI-radius) var(--MI-radius); } } > article { - background-color: var(--panel); + background-color: var(--MI_THEME-panel); padding: 16px; - border-radius: var(--radius); + border-radius: var(--MI-radius); > header { margin-bottom: 8px; @@ -114,7 +114,6 @@ const props = defineProps<{ > p { display: inline-block; margin: 0; - color: var(--urlPreviewInfo); font-size: 0.8em; line-height: 16px; vertical-align: top; diff --git a/packages/frontend/src/components/MkPageWindow.vue b/packages/frontend/src/components/MkPageWindow.vue index f67a1e5b63..84189211b6 100644 --- a/packages/frontend/src/components/MkPageWindow.vue +++ b/packages/frontend/src/components/MkPageWindow.vue @@ -13,7 +13,7 @@ SPDX-License-Identifier: AGPL-3.0-only :buttonsLeft="buttonsLeft" :buttonsRight="buttonsRight" :contextmenu="contextmenu" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header> <template v-if="pageMetadata"> @@ -30,17 +30,17 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, onMounted, onUnmounted, provide, ref, shallowRef } from 'vue'; +import { url } from '@@/js/config.js'; +import { getScrollContainer } from '@@/js/scroll.js'; import RouterView from '@/components/global/RouterView.vue'; import MkWindow from '@/components/MkWindow.vue'; import { popout as _popout } from '@/scripts/popout.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; -import { url } from '@@/js/config.js'; import { useScrollPositionManager } from '@/nirax.js'; import { i18n } from '@/i18n.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { openingWindowsCount } from '@/os.js'; import { claimAchievement } from '@/scripts/achievements.js'; -import { getScrollContainer } from '@@/js/scroll.js'; import { useRouterFactory } from '@/router/supplier.js'; import { mainRouter } from '@/router/main.js'; import MkUserName from './global/MkUserName.vue'; @@ -49,7 +49,7 @@ const props = defineProps<{ initialPath: string; }>(); -defineEmits<{ +const emit = defineEmits<{ (ev: 'closed'): void; }>(); @@ -59,7 +59,7 @@ const windowRouter = routerFactory(props.initialPath); const contents = shallowRef<HTMLElement | null>(null); const pageMetadata = ref<null | PageMetadata>(null); const windowEl = shallowRef<InstanceType<typeof MkWindow>>(); -const history = ref<{ path: string; key: any; }[]>([{ +const history = ref<{ path: string; key: string; }[]>([{ path: windowRouter.getCurrentPath(), key: windowRouter.getCurrentKey(), }]); @@ -181,8 +181,8 @@ defineExpose({ overscroll-behavior: contain; min-height: 100%; - background: var(--bg); + background: var(--MI_THEME-bg); - --margin: var(--marginHalf); + --MI-margin: var(--MI-marginHalf); } </style> diff --git a/packages/frontend/src/components/MkPoll.vue b/packages/frontend/src/components/MkPoll.vue index 592a511fb0..a414676bda 100644 --- a/packages/frontend/src/components/MkPoll.vue +++ b/packages/frontend/src/components/MkPoll.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <li v-for="(choice, i) in props.poll.choices" :key="i" :class="$style.choice" @click="vote(i)"> <div :class="$style.bg" :style="{ 'width': `${showResult ? (choice.votes / total * 100) : 0}%` }"></div> <span :class="$style.fg"> - <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--accent);"></i></template> + <template v-if="choice.isVoted"><i class="ti ti-check" style="margin-right: 4px; color: var(--MI_THEME-accent);"></i></template> <Mfm :text="choice.text" :plain="true"/> <span v-if="showResult" style="margin-left: 4px; opacity: 0.7;">({{ i18n.tsx._poll.votesCount({ n: choice.votes }) }})</span> </span> @@ -18,7 +18,7 @@ SPDX-License-Identifier: AGPL-3.0-only <p v-if="!readOnly" :class="$style.info"> <span>{{ i18n.tsx._poll.totalVotes({ n: total }) }}</span> <span v-if="poll.multiple"> · </span> - <span v-if="poll.multiple" style="color: var(--accent); font-weight: bolder;">{{ i18n.ts._poll.multiple }}</span> + <span v-if="poll.multiple" style="color: var(--MI_THEME-accent); font-weight: bolder;">{{ i18n.ts._poll.multiple }}</span> <span> · </span> <a v-if="!closed && !isVoted" style="color: inherit;" @click="showResult = !showResult">{{ showResult ? i18n.ts._poll.vote : i18n.ts._poll.showResult }}</a> <span v-if="isVoted">{{ i18n.ts._poll.voted }}</span> @@ -33,14 +33,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { host } from '@@/js/config.js'; +import { useInterval } from '@@/js/use-interval.js'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { sum } from '@/scripts/array.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; -import { host } from '@@/js/config.js'; -import { useInterval } from '@@/js/use-interval.js'; import { $i } from '@/account.js'; const props = defineProps<{ @@ -91,7 +91,7 @@ if (props.poll.expiresAt) { const vote = async (id) => { if (props.readOnly || closed.value || isVoted.value) return; - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (!props.poll.multiple) { const { canceled } = await os.confirm({ @@ -115,7 +115,7 @@ const vote = async (id) => { }; const refreshVotes = async () => { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (props.readOnly || closed.value) return; await misskeyApi('notes/polls/refresh', { @@ -139,9 +139,9 @@ const refreshVotes = async () => { position: relative; margin: 4px 0; padding: 4px; - //border: solid 0.5px var(--divider); - background: var(--accentedBg); - border-radius: var(--radius-xs); + //border: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-accentedBg); + border-radius: var(--MI-radius-xs); overflow: clip; cursor: pointer; } @@ -151,8 +151,8 @@ const refreshVotes = async () => { top: 0; left: 0; height: 100%; - background: var(--accent); - background: linear-gradient(90deg,var(--buttonGradateA),var(--buttonGradateB)); + background: var(--MI_THEME-accent); + background: linear-gradient(90deg,var(--MI_THEME-buttonGradateA),var(--MI_THEME-buttonGradateB)); transition: width 1s ease; } @@ -160,12 +160,12 @@ const refreshVotes = async () => { position: relative; display: inline-block; padding: 3px 5px; - background: var(--panel); - border-radius: var(--radius-xs); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-xs); } .info { - color: var(--fg); + color: var(--MI_THEME-fg); } .done { diff --git a/packages/frontend/src/components/MkPopupMenu.vue b/packages/frontend/src/components/MkPopupMenu.vue index d14873008b..f1b5ff4de0 100644 --- a/packages/frontend/src/components/MkPopupMenu.vue +++ b/packages/frontend/src/components/MkPopupMenu.vue @@ -19,7 +19,7 @@ defineProps<{ items: MenuItem[]; align?: 'center' | string; width?: number; - src?: any; + src?: HTMLElement | null; returnFocusTo?: HTMLElement | null; }>(); @@ -73,7 +73,7 @@ function close() { <style lang="scss" module> .drawer { - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); border-bottom-right-radius: 0; border-bottom-left-radius: 0; } diff --git a/packages/frontend/src/components/MkPostForm.vue b/packages/frontend/src/components/MkPostForm.vue index 93328fe52f..11ae6dbd6a 100644 --- a/packages/frontend/src/components/MkPostForm.vue +++ b/packages/frontend/src/components/MkPostForm.vue @@ -61,17 +61,17 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAcct :user="u"/> <button class="_button" style="padding: 4px 8px;" @click="removeVisibleUser(u)"><i class="ti ti-x"></i></button> </span> - <button class="_buttonPrimary" style="padding: 4px; border-radius: var(--radius-sm);" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button> + <button class="_buttonPrimary" style="padding: 4px; border-radius: var(--MI-radius-sm);" @click="addVisibleUser"><i class="ti ti-plus ti-fw"></i></button> </div> </div> <MkInfo v-if="hasNotSpecifiedMentions" warn :class="$style.hasNotSpecifiedMentions">{{ i18n.ts.notSpecifiedMentionWarning }} - <button class="_textButton" @click="addMissingMention()">{{ i18n.ts.add }}</button></MkInfo> <div v-show="useCw" :class="$style.cwFrame"> - <input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown"> + <input ref="cwInputEl" v-model="cw" :class="$style.cw" :placeholder="i18n.ts.annotation" @keydown="onKeydown" @keyup="onKeyup" @compositionend="onCompositionEnd"> <div v-if="maxCwLength - cwLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: cwLength > maxCwLength }]">{{ maxCwLength - cwLength }}</div> </div> <div :class="[$style.textOuter, { [$style.withCw]: useCw }]"> <div v-if="channel" :class="$style.colorBar" :style="{ background: channel.color }"></div> - <textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text dir="auto" @keydown="onKeydown" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> + <textarea ref="textareaEl" v-model="text" :class="[$style.text]" :disabled="posting || posted" :readonly="textAreaReadOnly" :placeholder="placeholder" data-cy-post-form-text dir="auto" @keydown="onKeydown" @keyup="onKeyup" @paste="onPaste" @compositionupdate="onCompositionUpdate" @compositionend="onCompositionEnd"/> <div v-if="maxTextLength - textLength < 100" :class="['_acrylic', $style.textCount, { [$style.textOver]: textLength > maxTextLength }]">{{ maxTextLength - textLength }}</div> </div> <input v-show="withHashtags" ref="hashtagsInputEl" v-model="hashtags" :class="$style.hashtags" :placeholder="i18n.ts.hashtags" list="hashtags"> @@ -136,28 +136,14 @@ import { miLocalStorage } from '@/local-storage.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { emojiPicker } from '@/scripts/emoji-picker.js'; import { mfmFunctionPicker } from '@/scripts/mfm-function-picker.js'; +import type { PostFormProps } from '@/types/post-form.js'; import MkScheduleEditor from '@/components/MkScheduleEditor.vue'; const $i = signinRequired(); const modal = inject('modal'); -const props = withDefaults(defineProps<{ - reply?: Misskey.entities.Note; - renote?: Misskey.entities.Note; - channel?: Misskey.entities.Channel; // TODO - mention?: Misskey.entities.User; - specified?: Misskey.entities.UserDetailed; - initialText?: string; - initialCw?: string; - initialVisibility?: (typeof Misskey.noteVisibilities)[number]; - initialFiles?: Misskey.entities.DriveFile[]; - initialLocalOnly?: boolean; - initialVisibleUsers?: Misskey.entities.UserDetailed[]; - initialNote?: Misskey.entities.Note & { - isSchedule?: boolean, - }; - instant?: boolean; +const props = withDefaults(defineProps<PostFormProps & { fixed?: boolean; autofocus?: boolean; freezeAfterPosted?: boolean; @@ -212,6 +198,7 @@ const recentHashtags = ref(JSON.parse(miLocalStorage.getItem('hashtags') ?? '[]' const imeText = ref(''); const showingOptions = ref(false); const textAreaReadOnly = ref(false); +const justEndedComposition = ref(false); const scheduleNote = ref<{ scheduledAt: number | null; } | null>(null); @@ -602,7 +589,13 @@ function clear() { function onKeydown(ev: KeyboardEvent) { if (ev.key === 'Enter' && (ev.ctrlKey || ev.metaKey) && canPost.value) post(); - if (ev.key === 'Escape') emit('esc'); + // justEndedComposition.value is for Safari, which keyDown occurs after compositionend. + // ev.isComposing is for another browsers. + if (ev.key === 'Escape' && !justEndedComposition.value && !ev.isComposing) emit('esc'); +} + +function onKeyup(ev: KeyboardEvent) { + justEndedComposition.value = false; } function onCompositionUpdate(ev: CompositionEvent) { @@ -611,6 +604,7 @@ function onCompositionUpdate(ev: CompositionEvent) { function onCompositionEnd(ev: CompositionEvent) { imeText.value = ''; + justEndedComposition.value = true; } async function onPaste(ev: ClipboardEvent) { @@ -1015,8 +1009,8 @@ function showActions(ev: MouseEvent) { action.handler({ text: text.value, cw: cw.value, - }, (key, value: any) => { - if (typeof key !== 'string') return; + }, (key, value) => { + if (typeof key !== 'string' || typeof value !== 'string') return; if (key === 'text') { text.value = value; } if (key === 'cw') { useCw.value = value !== null; cw.value = value; } }); @@ -1228,8 +1222,8 @@ defineExpose({ &:focus-visible { outline: none; - .submitInner { - outline: 2px solid var(--fgOnAccent); + > .submitInner { + outline: 2px solid var(--MI_THEME-fgOnAccent); outline-offset: -4px; } } @@ -1243,14 +1237,14 @@ defineExpose({ } &:not(:disabled):hover { - > .inner { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + > .submitInner { + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } &:not(:disabled):active { - > .inner { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + > .submitInner { + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } } @@ -1261,7 +1255,7 @@ defineExpose({ left: 12px; width: 5px; height: 100% ; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); pointer-events: none; } @@ -1269,20 +1263,20 @@ defineExpose({ padding: 0 12px; line-height: 34px; font-weight: bold; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); min-width: 90px; box-sizing: border-box; - color: var(--fgOnAccent); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } .headerRightItem { margin: 0; padding: 8px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); &:hover { - background: var(--X5); + background: var(--MI_THEME-X5); } &:disabled { @@ -1326,7 +1320,7 @@ defineExpose({ .withQuote { margin: 0 0 8px 0; - color: var(--accent); + color: var(--MI_THEME-accent); } .toSpecified { @@ -1345,8 +1339,8 @@ defineExpose({ .visibleUser { margin-right: 14px; padding: 8px 0 8px 8px; - border-radius: var(--radius-sm); - background: var(--X4); + border-radius: var(--MI-radius-sm); + background: var(--MI_THEME-X4); } .hasNotSpecifiedMentions { @@ -1365,7 +1359,7 @@ defineExpose({ border: none; border-radius: 0; background: transparent; - color: var(--fg); + color: var(--MI_THEME-fg); font-family: inherit; &:focus { @@ -1380,7 +1374,7 @@ defineExpose({ .cwFrame { z-index: 1; padding-bottom: 8px; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); width: 100%; position: relative; @@ -1390,7 +1384,7 @@ defineExpose({ z-index: 1; padding-top: 8px; padding-bottom: 8px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .textOuter { @@ -1416,8 +1410,8 @@ defineExpose({ right: 2px; padding: 4px 6px; font-size: .9em; - color: var(--warn); - border-radius: var(--radius-sm); + color: var(--MI_THEME-warn); + border-radius: var(--MI-radius-sm); min-width: 1.6em; text-align: center; @@ -1457,19 +1451,19 @@ defineExpose({ font-size: 1em; width: auto; height: 100%; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); &:hover { - background: var(--X5); + background: var(--MI_THEME-X5); } &.footerButtonActive { - color: var(--accent); + color: var(--MI_THEME-accent); } } .previewButtonActive { - color: var(--accent); + color: var(--MI_THEME-accent); } @container (max-width: 500px) { diff --git a/packages/frontend/src/components/MkPostFormAttaches.vue b/packages/frontend/src/components/MkPostFormAttaches.vue index f90fcfef33..11444d8d78 100644 --- a/packages/frontend/src/components/MkPostFormAttaches.vue +++ b/packages/frontend/src/components/MkPostFormAttaches.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div v-show="props.modelValue.length != 0" :class="$style.root"> <Sortable :modelValue="props.modelValue" :class="$style.files" itemKey="id" :animation="150" :delay="100" :delayOnTouchOnly="true" @update:modelValue="v => emit('update:modelValue', v)"> - <template #item="{element}"> + <template #item="{ element }"> <div :class="$style.file" role="button" @@ -38,14 +38,14 @@ import type { MenuItem } from '@/types/menu.js'; const Sortable = defineAsyncComponent(() => import('vuedraggable').then(x => x.default)); const props = defineProps<{ - modelValue: any[]; + modelValue: Misskey.entities.DriveFile[]; detachMediaFn?: (id: string) => void; }>(); const mock = inject<boolean>('mock', false); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any[]): void; + (ev: 'update:modelValue', value: Misskey.entities.DriveFile[]): void; (ev: 'detach', id: string): void; (ev: 'changeSensitive', file: Misskey.entities.DriveFile, isSensitive: boolean): void; (ev: 'changeName', file: Misskey.entities.DriveFile, newName: string): void; @@ -113,7 +113,7 @@ async function rename(file) { }); } -async function describe(file) { +async function describe(file: Misskey.entities.DriveFile) { if (mock) return; const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkFileCaptionEditWindow.vue')), { @@ -203,7 +203,7 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar width: 64px; height: 64px; margin-right: 4px; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); overflow: hidden; cursor: move; @@ -216,7 +216,7 @@ function showFileMenu(file: Misskey.entities.DriveFile, ev: MouseEvent | Keyboar width: 100%; height: 100%; z-index: 1; - color: var(--fg); + color: var(--MI_THEME-fg); } .sensitive { diff --git a/packages/frontend/src/components/MkPostFormDialog.vue b/packages/frontend/src/components/MkPostFormDialog.vue index 811a6378f2..0fd17e12c7 100644 --- a/packages/frontend/src/components/MkPostFormDialog.vue +++ b/packages/frontend/src/components/MkPostFormDialog.vue @@ -11,23 +11,12 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { shallowRef } from 'vue'; -import * as Misskey from 'misskey-js'; import MkModal from '@/components/MkModal.vue'; import MkPostForm from '@/components/MkPostForm.vue'; +import * as Misskey from 'misskey-js'; +import type { PostFormProps } from '@/types/post-form.js'; -const props = withDefaults(defineProps<{ - reply?: Misskey.entities.Note; - renote?: Misskey.entities.Note; - channel?: any; // TODO - mention?: Misskey.entities.User; - specified?: Misskey.entities.UserDetailed; - initialText?: string; - initialCw?: string; - initialVisibility?: (typeof Misskey.noteVisibilities)[number]; - initialFiles?: Misskey.entities.DriveFile[]; - initialLocalOnly?: boolean; - initialVisibleUsers?: Misskey.entities.UserDetailed[]; - initialNote?: Misskey.entities.Note; +const props = withDefaults(defineProps<PostFormProps & { instant?: boolean; fixed?: boolean; autofocus?: boolean; diff --git a/packages/frontend/src/components/MkRadio.vue b/packages/frontend/src/components/MkRadio.vue index e02f76a58f..5bd50170d8 100644 --- a/packages/frontend/src/components/MkRadio.vue +++ b/packages/frontend/src/components/MkRadio.vue @@ -24,17 +24,17 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> -<script lang="ts" setup> +<script lang="ts" setup generic="T extends unknown"> import { computed } from 'vue'; const props = defineProps<{ - modelValue: any; - value: any; + modelValue: T; + value: T; disabled?: boolean; }>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: T): void; }>(); const checked = computed(() => props.modelValue === props.value); @@ -53,10 +53,10 @@ function toggle(): void { cursor: pointer; padding: 7px 10px; min-width: 60px; - background-color: var(--panel); + background-color: var(--MI_THEME-panel); background-clip: padding-box !important; - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); font-size: 90%; transition: all 0.2s; user-select: none; @@ -67,25 +67,25 @@ function toggle(): void { } &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } &:focus-within { outline: none; - box-shadow: 0 0 0 2px var(--focus); + box-shadow: 0 0 0 2px var(--MI_THEME-focus); } &.checked { - background-color: var(--accentedBg) !important; - border-color: var(--accentedBg) !important; - color: var(--accent); + background-color: var(--MI_THEME-accentedBg) !important; + border-color: var(--MI_THEME-accentedBg) !important; + color: var(--MI_THEME-accent); cursor: default !important; > .button { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); &::after { - background-color: var(--accent); + background-color: var(--MI_THEME-accent); transform: scale(1); opacity: 1; } @@ -106,8 +106,8 @@ function toggle(): void { width: 14px; height: 14px; background: none; - border: solid 2px var(--inputBorder); - border-radius: var(--radius-full); + border: solid 2px var(--MI_THEME-inputBorder); + border-radius: var(--MI-radius-full); transition: inherit; &::after { @@ -118,7 +118,7 @@ function toggle(): void { right: 3px; bottom: 3px; left: 3px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); opacity: 0; transform: scale(0); transition: 0.4s cubic-bezier(0.25, 0.8, 0.25, 1); diff --git a/packages/frontend/src/components/MkRadios.vue b/packages/frontend/src/components/MkRadios.vue index 705c93f770..af81eb814d 100644 --- a/packages/frontend/src/components/MkRadios.vue +++ b/packages/frontend/src/components/MkRadios.vue @@ -77,7 +77,7 @@ export default defineComponent({ > .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; diff --git a/packages/frontend/src/components/MkRange.vue b/packages/frontend/src/components/MkRange.vue index 22c187c357..d009f3858c 100644 --- a/packages/frontend/src/components/MkRange.vue +++ b/packages/frontend/src/components/MkRange.vue @@ -212,7 +212,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { > .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -224,9 +224,9 @@ function onMousedown(ev: MouseEvent | TouchEvent) { > .body { padding: 7px 12px; - background: var(--panel); - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); > .container { position: relative; @@ -242,7 +242,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { width: calc(100% - #{$thumbWidth}); height: 3px; background: rgba(0, 0, 0, 0.1); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); overflow: clip; > .highlight { @@ -250,7 +250,7 @@ function onMousedown(ev: MouseEvent | TouchEvent) { top: 0; left: 0; height: 100%; - background: var(--accent); + background: var(--MI_THEME-accent); opacity: 0.5; } } @@ -272,8 +272,8 @@ function onMousedown(ev: MouseEvent | TouchEvent) { width: $tickWidth; height: 3px; margin-left: - math.div($tickWidth, 2); - background: var(--divider); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-divider); + border-radius: var(--MI-radius-ellipse); } } @@ -282,11 +282,11 @@ function onMousedown(ev: MouseEvent | TouchEvent) { width: $thumbWidth; height: $thumbHeight; cursor: grab; - background: var(--accent); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-accent); + border-radius: var(--MI-radius-ellipse); &:hover { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } } } diff --git a/packages/frontend/src/components/MkReactionEffect.vue b/packages/frontend/src/components/MkReactionEffect.vue index 361e246e9f..5a59a5e055 100644 --- a/packages/frontend/src/components/MkReactionEffect.vue +++ b/packages/frontend/src/components/MkReactionEffect.vue @@ -60,7 +60,7 @@ onMounted(() => { right: 0; bottom: 0; margin: auto; - color: var(--accent); + color: var(--MI_THEME-accent); font-size: 18px; font-weight: bold; transform: translateY(-30px); diff --git a/packages/frontend/src/components/MkReactionsViewer.details.vue b/packages/frontend/src/components/MkReactionsViewer.details.vue index 6fdeb3a3ab..e60ac86315 100644 --- a/packages/frontend/src/components/MkReactionsViewer.details.vue +++ b/packages/frontend/src/components/MkReactionsViewer.details.vue @@ -23,6 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { } from 'vue'; +import * as Misskey from 'misskey-js'; import { getEmojiName } from '@@/js/emojilist.js'; import MkTooltip from './MkTooltip.vue'; import MkReactionIcon from '@/components/MkReactionIcon.vue'; @@ -30,7 +31,7 @@ import MkReactionIcon from '@/components/MkReactionIcon.vue'; defineProps<{ showing: boolean; reaction: string; - users: any[]; // TODO + users: Misskey.entities.UserLite[]; count: number; targetElement: HTMLElement; }>(); @@ -57,7 +58,7 @@ function getReactionName(reaction: string): string { max-width: 100px; padding-right: 10px; text-align: center; - border-right: solid 0.5px var(--divider); + border-right: solid 0.5px var(--MI_THEME-divider); } .reactionIcon { diff --git a/packages/frontend/src/components/MkReactionsViewer.reaction.vue b/packages/frontend/src/components/MkReactionsViewer.reaction.vue index 957ee0e76b..9cd972639f 100644 --- a/packages/frontend/src/components/MkReactionsViewer.reaction.vue +++ b/packages/frontend/src/components/MkReactionsViewer.reaction.vue @@ -175,12 +175,12 @@ if (!mock) { margin: 2px; padding: 0 6px; font-size: 1.5em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); align-items: center; justify-content: center; &.canToggle { - background: var(--buttonBg); + background: var(--MI_THEME-buttonBg); &:hover { background: rgba(0, 0, 0, 0.1); @@ -194,7 +194,7 @@ if (!mock) { &.small { height: 32px; font-size: 1em; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); > .count { font-size: 0.9em; @@ -205,7 +205,7 @@ if (!mock) { &.large { height: 52px; font-size: 2em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); > .count { font-size: 0.6em; @@ -214,12 +214,12 @@ if (!mock) { } &.reacted, &.reacted:hover { - background: var(--accentedBg); - color: var(--accent); - box-shadow: 0 0 0 1px var(--accent) inset; + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + box-shadow: 0 0 0 1px var(--MI_THEME-accent) inset; > .count { - color: var(--accent); + color: var(--MI_THEME-accent); } > .icon { diff --git a/packages/frontend/src/components/MkRemoteCaution.vue b/packages/frontend/src/components/MkRemoteCaution.vue index 2b59eab9d9..6391468204 100644 --- a/packages/frontend/src/components/MkRemoteCaution.vue +++ b/packages/frontend/src/components/MkRemoteCaution.vue @@ -19,15 +19,15 @@ defineProps<{ .root { font-size: 0.8em; padding: 16px; - background: color-mix(in srgb, var(--infoWarnBg) 65%, transparent); - color: var(--infoWarnFg); - border-radius: var(--radius); + background: color-mix(in srgb, var(--MI_THEME-infoWarnBg) 65%, transparent); + color: var(--MI_THEME-infoWarnFg); + border-radius: var(--MI-radius); overflow: clip; z-index: 1; } .link { margin-left: 4px; - color: var(--accent); + color: var(--MI_THEME-accent); } </style> diff --git a/packages/frontend/src/components/MkRetentionLineChart.vue b/packages/frontend/src/components/MkRetentionLineChart.vue index c3daa9c9a4..d41793b0fa 100644 --- a/packages/frontend/src/components/MkRetentionLineChart.vue +++ b/packages/frontend/src/components/MkRetentionLineChart.vue @@ -44,7 +44,7 @@ onMounted(async () => { const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; - const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--accent')); + const accent = tinycolor(getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-accent')); const color = accent.toHex(); if (chartEl.value == null) return; diff --git a/packages/frontend/src/components/MkRippleEffect.vue b/packages/frontend/src/components/MkRippleEffect.vue index ee5bb73ebf..2949cf156d 100644 --- a/packages/frontend/src/components/MkRippleEffect.vue +++ b/packages/frontend/src/components/MkRippleEffect.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div :class="$style.root" :style="{ zIndex, top: `${y - 64}px`, left: `${x - 64}px` }"> <svg width="128" height="128" viewBox="0 0 128 128" xmlns="http://www.w3.org/2000/svg"> - <circle fill="none" cx="64" cy="64" style="stroke: var(--accent);"> + <circle fill="none" cx="64" cy="64" style="stroke: var(--MI_THEME-accent);"> <animate attributeName="r" begin="0s" dur="0.5s" @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only /> </circle> <g fill="none" fill-rule="evenodd"> - <circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--accent);"> + <circle v-for="(particle, i) in particles" :key="i" :fill="particle.color" style="stroke: var(--MI_THEME-accent);"> <animate attributeName="r" begin="0s" dur="0.8s" diff --git a/packages/frontend/src/components/MkRolePreview.vue b/packages/frontend/src/components/MkRolePreview.vue index ce17ae08e0..3f14c5b5e0 100644 --- a/packages/frontend/src/components/MkRolePreview.vue +++ b/packages/frontend/src/components/MkRolePreview.vue @@ -6,8 +6,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkA :to="forModeration ? `/admin/roles/${role.id}` : `/roles/${role.id}`" :class="$style.root" tabindex="-1" :style="{ '--color': role.color }"> <template v-if="forModeration"> - <i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--success)"></i> - <i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--warn)"></i> + <i v-if="role.isPublic" class="ti ti-world" :class="$style.icon" style="color: var(--MI_THEME-success)"></i> + <i v-else class="ti ti-lock" :class="$style.icon" style="color: var(--MI_THEME-warn)"></i> </template> <div v-adaptive-bg class="_panel" :class="$style.body"> @@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only <img :class="$style.bodyBadge" :src="role.iconUrl"/> </template> <template v-else> - <i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--accent);"></i> - <i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--accent);"></i> + <i v-if="role.isAdministrator" class="ti ti-crown" style="color: var(--MI_THEME-accent);"></i> + <i v-else-if="role.isModerator" class="ti ti-shield" style="color: var(--MI_THEME-accent);"></i> <i v-else class="ti ti-user" style="opacity: 0.7;"></i> </template> </span> diff --git a/packages/frontend/src/components/MkSelect.vue b/packages/frontend/src/components/MkSelect.vue index 150a5c6d54..79a56b68a8 100644 --- a/packages/frontend/src/components/MkSelect.vue +++ b/packages/frontend/src/components/MkSelect.vue @@ -16,9 +16,8 @@ SPDX-License-Identifier: AGPL-3.0-only @keydown.space.enter="show" > <div ref="prefixEl" :class="$style.prefix"><slot name="prefix"></slot></div> - <select + <div ref="inputEl" - v-model="v" v-adaptive-border tabindex="-1" :class="$style.inputCore" @@ -26,55 +25,48 @@ SPDX-License-Identifier: AGPL-3.0-only :required="required" :readonly="readonly" :placeholder="placeholder" - @input="onInput" @mousedown.prevent="() => {}" @keydown.prevent="() => {}" > - <slot></slot> - </select> + <div style="pointer-events: none;">{{ currentValueText ?? '' }}</div> + <div style="display: none;"> + <slot></slot> + </div> + </div> <div ref="suffixEl" :class="$style.suffix"><i class="ti ti-chevron-down" :class="[$style.chevron, { [$style.chevronOpening]: opening }]"></i></div> </div> <div :class="$style.caption"><slot name="caption"></slot></div> - - <MkButton v-if="manualSave && changed" primary :class="$style.save" @click="updated"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> </div> </template> <script lang="ts" setup> import { onMounted, nextTick, ref, watch, computed, toRefs, VNode, useSlots, VNodeChild } from 'vue'; -import MkButton from '@/components/MkButton.vue'; -import * as os from '@/os.js'; import { useInterval } from '@@/js/use-interval.js'; -import { i18n } from '@/i18n.js'; import type { MenuItem } from '@/types/menu.js'; +import * as os from '@/os.js'; const props = defineProps<{ - modelValue: string | null; + modelValue: string | number | null; required?: boolean; readonly?: boolean; disabled?: boolean; placeholder?: string; autofocus?: boolean; inline?: boolean; - manualSave?: boolean; small?: boolean; large?: boolean; }>(); const emit = defineEmits<{ - (ev: 'changeByUser', value: string | null): void; - (ev: 'update:modelValue', value: string | null): void; + (ev: 'update:modelValue', value: string | number | null): void; }>(); const slots = useSlots(); const { modelValue, autofocus } = toRefs(props); -const v = ref(modelValue.value); const focused = ref(false); const opening = ref(false); -const changed = ref(false); -const invalid = ref(false); -const filled = computed(() => v.value !== '' && v.value != null); +const currentValueText = ref<string | null>(null); const inputEl = ref<HTMLObjectElement | null>(null); const prefixEl = ref<HTMLElement | null>(null); const suffixEl = ref<HTMLElement | null>(null); @@ -85,26 +77,6 @@ const height = 36; const focus = () => container.value?.focus(); -const onInput = (ev) => { - changed.value = true; -}; - -const updated = () => { - changed.value = false; - emit('update:modelValue', v.value); -}; - -watch(modelValue, newValue => { - v.value = newValue; -}); - -watch(v, () => { - if (!props.manualSave) { - updated(); - } - - invalid.value = inputEl.value?.validity.badInput ?? true; -}); // ã“ã®ã‚³ãƒ³ãƒãƒ¼ãƒãƒ³ãƒˆãŒä½œæˆã•ã‚ŒãŸæ™‚ã€éžè¡¨ç¤ºçŠ¶æ…‹ã§ã‚ã‚‹å ´åˆãŒã‚ã‚‹ // éžè¡¨ç¤ºçŠ¶æ…‹ã ã¨è¦ç´ ã®å¹…ãªã©ã¯0ã«ãªã£ã¦ã—ã¾ã†ã®ã§ã€å®šæœŸçš„ã«è¨ˆç®—ã™ã‚‹ @@ -134,6 +106,31 @@ onMounted(() => { }); }); +watch(modelValue, () => { + const scanOptions = (options: VNodeChild[]) => { + for (const vnode of options) { + if (typeof vnode !== 'object' || vnode === null || Array.isArray(vnode)) continue; + if (vnode.type === 'optgroup') { + const optgroup = vnode; + if (Array.isArray(optgroup.children)) scanOptions(optgroup.children); + } else if (Array.isArray(vnode.children)) { // 何故ã‹ãƒ•ラグメントã«ãªã£ã¦ãã‚‹ã“ã¨ãŒã‚ã‚‹ + const fragment = vnode; + if (Array.isArray(fragment.children)) scanOptions(fragment.children); + } else if (vnode.props == null) { // v-if ã§æ¡ä»¶ãŒ false ã®ã¨ãã«ã“ã†ãªã‚‹ + // nop? + } else { + const option = vnode; + if (option.props?.value === modelValue.value) { + currentValueText.value = option.children as string; + break; + } + } + } + }; + + scanOptions(slots.default!()); +}, { immediate: true }); + function show() { if (opening.value) return; focus(); @@ -146,11 +143,9 @@ function show() { const pushOption = (option: VNode) => { menu.push({ text: option.children as string, - active: computed(() => v.value === option.props?.value), + active: computed(() => modelValue.value === option.props?.value), action: () => { - v.value = option.props?.value; - changed.value = true; - emit('changeByUser', v.value); + emit('update:modelValue', option.props?.value); }, }); }; @@ -202,7 +197,7 @@ function show() { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -220,8 +215,8 @@ function show() { &.focused { > .inputCore { - border-color: var(--accent) !important; - //box-shadow: 0 0 0 4px var(--focus); + border-color: var(--MI_THEME-accent) !important; + //box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } @@ -240,7 +235,7 @@ function show() { &:hover { > .inputCore { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } } @@ -248,7 +243,8 @@ function show() { .inputCore { appearance: none; -webkit-appearance: none; - display: block; + display: flex; + align-items: center; height: v-bind("height + 'px'"); width: 100%; margin: 0; @@ -256,10 +252,10 @@ function show() { font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); outline: none; box-shadow: none; box-sizing: border-box; diff --git a/packages/frontend/src/components/MkSignin.input.vue b/packages/frontend/src/components/MkSignin.input.vue new file mode 100644 index 0000000000..34c22abc31 --- /dev/null +++ b/packages/frontend/src/components/MkSignin.input.vue @@ -0,0 +1,206 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.wrapper" data-cy-signin-page-input> + <div :class="$style.root"> + <div :class="$style.avatar"> + <i class="ti ti-user"></i> + </div> + + <!-- ãƒã‚°ã‚¤ãƒ³ç”»é¢ãƒ¡ãƒƒã‚»ãƒ¼ã‚¸ --> + <MkInfo v-if="message"> + {{ message }} + </MkInfo> + + <!-- 外部サーãƒãƒ¼ã¸ã®è»¢é€ --> + <div v-if="openOnRemote" class="_gaps_m"> + <div class="_gaps_s"> + <MkButton type="button" rounded primary style="margin: 0 auto;" @click="openRemote(openOnRemote)"> + {{ i18n.ts.continueOnRemote }} <i class="ti ti-external-link"></i> + </MkButton> + <button type="button" class="_button" :class="$style.instanceManualSelectButton" @click="specifyHostAndOpenRemote(openOnRemote)"> + {{ i18n.ts.specifyServerHost }} + </button> + </div> + <div :class="$style.orHr"> + <p :class="$style.orMsg">{{ i18n.ts.or }}</p> + </div> + </div> + + <!-- username入力 --> + <form class="_gaps_s" @submit.prevent="emit('usernameSubmitted', username)"> + <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username> + <template #prefix>@</template> + <template #suffix>@{{ host }}</template> + </MkInput> + <MkButton type="submit" large primary rounded style="margin: 0 auto;" data-cy-signin-page-input-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </form> + + <!-- パスワードレスãƒã‚°ã‚¤ãƒ³ --> + <div :class="$style.orHr"> + <p :class="$style.orMsg">{{ i18n.ts.or }}</p> + </div> + <div> + <MkButton type="submit" style="margin: auto auto;" large rounded primary gradate @click="emit('passkeyClick', $event)"> + <i class="ti ti-device-usb" style="font-size: medium;"></i>{{ i18n.ts.signinWithPasskey }} + </MkButton> + </div> + </div> +</div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue'; +import { toUnicode } from 'punycode/'; + +import { query, extractDomain } from '@@/js/url.js'; +import { host as configHost } from '@@/js/config.js'; +import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; + +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkInfo from '@/components/MkInfo.vue'; + +const props = withDefaults(defineProps<{ + message?: string, + openOnRemote?: OpenOnRemoteOptions, +}>(), { + message: '', + openOnRemote: undefined, +}); + +const emit = defineEmits<{ + (ev: 'usernameSubmitted', v: string): void; + (ev: 'passkeyClick', v: MouseEvent): void; +}>(); + +const host = toUnicode(configHost); + +const username = ref(''); + +//#region Open on remote +function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void { + switch (options.type) { + case 'web': + case 'lookup': { + let _path: string; + + if (options.type === 'lookup') { + // TODO: v2024.7.0以é™ãŒæµ¸é€ã—ã¦ããŸã‚‰æ£å¼ãªURLã«å¤‰æ›´ã™ã‚‹â–¼ + // _path = `/lookup?uri=${encodeURIComponent(_path)}`; + _path = `/authorize-follow?acct=${encodeURIComponent(options.url)}`; + } else { + _path = options.path; + } + + if (targetHost) { + window.open(`https://${targetHost}${_path}`, '_blank', 'noopener'); + } else { + window.open(`https://misskey-hub.net/mi-web/?path=${encodeURIComponent(_path)}`, '_blank', 'noopener'); + } + break; + } + case 'share': { + const params = query(options.params); + if (targetHost) { + window.open(`https://${targetHost}/share?${params}`, '_blank', 'noopener'); + } else { + window.open(`https://misskey-hub.net/share/?${params}`, '_blank', 'noopener'); + } + break; + } + } +} + +async function specifyHostAndOpenRemote(options: OpenOnRemoteOptions): Promise<void> { + const { canceled, result: hostTemp } = await os.inputText({ + title: i18n.ts.inputHostName, + placeholder: 'misskey.example.com', + }); + + if (canceled) return; + + let targetHost: string | null = hostTemp; + + // ドメイン部分ã ã‘ã‚’å–り出㙠+ targetHost = extractDomain(targetHost ?? ''); + if (targetHost == null) { + os.alert({ + type: 'error', + title: i18n.ts.invalidValue, + text: i18n.ts.tryAgain, + }); + return; + } + openRemote(options, targetHost); +} +//#endregion +</script> + +<style lang="scss" module> +.root { + display: flex; + flex-direction: column; + gap: 20px; +} + +.wrapper { + display: flex; + align-items: center; + width: 100%; + min-height: 336px; + + > .root { + width: 100%; + } +} + +.avatar { + margin: 0 auto; + background-color: color-mix(in srgb, var(--MI_THEME-fg), transparent 85%); + color: color-mix(in srgb, var(--MI_THEME-fg), transparent 25%); + text-align: center; + height: 64px; + width: 64px; + font-size: 24px; + line-height: 64px; + border-radius: 50%; +} + +.instanceManualSelectButton { + display: block; + text-align: center; + opacity: .7; + font-size: .8em; + + &:hover { + text-decoration: underline; + } +} + +.orHr { + position: relative; + margin: .4em auto; + width: 100%; + height: 1px; + background: var(--MI_THEME-divider); +} + +.orMsg { + position: absolute; + top: -.6em; + display: inline-block; + padding: 0 1em; + background: var(--MI_THEME-panel); + font-size: 0.8em; + color: var(--MI_THEME-fgOnPanel); + margin: 0; + left: 50%; + transform: translateX(-50%); +} +</style> diff --git a/packages/frontend/src/components/MkSignin.passkey.vue b/packages/frontend/src/components/MkSignin.passkey.vue new file mode 100644 index 0000000000..e5a56ab66d --- /dev/null +++ b/packages/frontend/src/components/MkSignin.passkey.vue @@ -0,0 +1,92 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.wrapper"> + <div class="_gaps" :class="$style.root"> + <div class="_gaps_s"> + <div :class="$style.passkeyIcon"> + <i class="ti ti-fingerprint"></i> + </div> + <div :class="$style.passkeyDescription">{{ i18n.ts.useSecurityKey }}</div> + </div> + + <MkButton large primary rounded :disabled="queryingKey" style="margin: 0 auto;" @click="queryKey">{{ i18n.ts.retry }}</MkButton> + + <MkButton v-if="isPerformingPasswordlessLogin !== true" transparent rounded :disabled="queryingKey" style="margin: 0 auto;" @click="emit('useTotp')">{{ i18n.ts.useTotp }}</MkButton> + </div> +</div> +</template> + +<script setup lang="ts"> +import { ref, onMounted } from 'vue'; +import { get as webAuthnRequest } from '@github/webauthn-json/browser-ponyfill'; + +import { i18n } from '@/i18n.js'; + +import MkButton from '@/components/MkButton.vue'; + +import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill'; + +const props = defineProps<{ + credentialRequest: CredentialRequestOptions; + isPerformingPasswordlessLogin?: boolean; +}>(); + +const emit = defineEmits<{ + (ev: 'done', credential: AuthenticationPublicKeyCredential): void; + (ev: 'useTotp'): void; +}>(); + +const queryingKey = ref(true); + +async function queryKey() { + queryingKey.value = true; + await webAuthnRequest(props.credentialRequest) + .catch(() => { + return Promise.reject(null); + }) + .then((credential) => { + emit('done', credential); + }) + .finally(() => { + queryingKey.value = false; + }); +} + +onMounted(() => { + queryKey(); +}); +</script> + +<style lang="scss" module> +.wrapper { + display: flex; + align-items: center; + width: 100%; + min-height: 336px; + + > .root { + width: 100%; + } +} + +.passkeyIcon { + margin: 0 auto; + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + text-align: center; + height: 64px; + width: 64px; + font-size: 24px; + line-height: 64px; + border-radius: 50%; +} + +.passkeyDescription { + text-align: center; + font-size: 1.1em; +} +</style> diff --git a/packages/frontend/src/components/MkSignin.password.vue b/packages/frontend/src/components/MkSignin.password.vue new file mode 100644 index 0000000000..ff7c598b50 --- /dev/null +++ b/packages/frontend/src/components/MkSignin.password.vue @@ -0,0 +1,195 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.wrapper" data-cy-signin-page-password> + <div class="_gaps" :class="$style.root"> + <div :class="$style.avatar" :style="{ backgroundImage: user ? `url('${user.avatarUrl}')` : undefined }"></div> + <div :class="$style.welcomeBackMessage"> + <I18n :src="i18n.ts.welcomeBackWithName" tag="span"> + <template #name><Mfm :text="user.name ?? user.username" :plain="true"/></template> + </I18n> + </div> + + <!-- password入力 --> + <form class="_gaps_s" @submit.prevent="onSubmit"> + <!-- ブラウザ オートコンプリート用 --> + <input type="hidden" name="username" autocomplete="username" :value="user.username"> + + <MkInput v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required autofocus data-cy-signin-password> + <template #prefix><i class="ti ti-lock"></i></template> + <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> + </MkInput> + + <div v-if="needCaptcha"> + <MkCaptcha v-if="instance.enableHcaptcha" ref="hcaptcha" v-model="hCaptchaResponse" provider="hcaptcha" :sitekey="instance.hcaptchaSiteKey"/> + <MkCaptcha v-if="instance.enableMcaptcha" ref="mcaptcha" v-model="mCaptchaResponse" provider="mcaptcha" :sitekey="instance.mcaptchaSiteKey" :instanceUrl="instance.mcaptchaInstanceUrl"/> + <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> + <MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> + <MkCaptcha v-if="instance.enableFC" ref="fc" v-model="fcResponse" provider="fc" :sitekey="instance.fcSiteKey"/> + <MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" provider="testcaptcha"/> + </div> + + <MkButton type="submit" :disabled="needCaptcha && captchaFailed" large primary rounded style="margin: 0 auto;" data-cy-signin-page-password-continue>{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </form> + </div> +</div> +</template> + +<script lang="ts"> +export type PwResponse = { + password: string; + captcha: { + hCaptchaResponse: string | null; + mCaptchaResponse: string | null; + reCaptchaResponse: string | null; + turnstileResponse: string | null; + fcResponse: string | null; + testcaptchaResponse: string | null; + }; +}; +</script> + +<script setup lang="ts"> +import { ref, computed, useTemplateRef, defineAsyncComponent } from 'vue'; +import * as Misskey from 'misskey-js'; + +import { instance } from '@/instance.js'; +import { i18n } from '@/i18n.js'; +import * as os from '@/os.js'; + +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkCaptcha from '@/components/MkCaptcha.vue'; + +const props = defineProps<{ + user: Misskey.entities.UserDetailed; + needCaptcha: boolean; +}>(); + +const emit = defineEmits<{ + (ev: 'passwordSubmitted', v: PwResponse): void; +}>(); + +const password = ref(''); + +const hCaptcha = useTemplateRef('hcaptcha'); +const mCaptcha = useTemplateRef('mcaptcha'); +const reCaptcha = useTemplateRef('recaptcha'); +const turnstile = useTemplateRef('turnstile'); +const fc = useTemplateRef('fc'); +const testcaptcha = useTemplateRef('testcaptcha'); + +const hCaptchaResponse = ref<string | null>(null); +const mCaptchaResponse = ref<string | null>(null); +const reCaptchaResponse = ref<string | null>(null); +const turnstileResponse = ref<string | null>(null); +const fcResponse = ref<string | null>(null); +const testcaptchaResponse = ref<string | null>(null); + +const captchaFailed = computed((): boolean => { + return ( + (instance.enableHcaptcha && !hCaptchaResponse.value) || + (instance.enableMcaptcha && !mCaptchaResponse.value) || + (instance.enableRecaptcha && !reCaptchaResponse.value) || + (instance.enableTurnstile && !turnstileResponse.value) || + (instance.enableFC && !fcResponse.value) || + (instance.enableTestcaptcha && !testcaptchaResponse.value) + ); +}); + +function resetPassword(): void { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, { + closed: () => dispose(), + }); +} + +function onSubmit() { + emit('passwordSubmitted', { + password: password.value, + captcha: { + hCaptchaResponse: hCaptchaResponse.value, + mCaptchaResponse: mCaptchaResponse.value, + reCaptchaResponse: reCaptchaResponse.value, + turnstileResponse: turnstileResponse.value, + fcResponse: fcResponse.value, + testcaptchaResponse: testcaptchaResponse.value, + }, + }); +} + +function resetCaptcha() { + hCaptcha.value?.reset(); + mCaptcha.value?.reset(); + reCaptcha.value?.reset(); + turnstile.value?.reset(); + fc.value?.reset(); + testcaptcha.value?.reset(); +} + +defineExpose({ + resetCaptcha, +}); +</script> + +<style lang="scss" module> +.wrapper { + display: flex; + align-items: center; + width: 100%; + min-height: 336px; + + > .root { + width: 100%; + } +} + +.avatar { + margin: 0 auto 0 auto; + width: 64px; + height: 64px; + background: #ddd; + background-position: center; + background-size: cover; + border-radius: 100%; +} + +.welcomeBackMessage { + text-align: center; + font-size: 1.1em; +} + +.instanceManualSelectButton { + display: block; + text-align: center; + opacity: .7; + font-size: .8em; + + &:hover { + text-decoration: underline; + } +} + +.orHr { + position: relative; + margin: .4em auto; + width: 100%; + height: 1px; + background: var(--MI_THEME-divider); +} + +.orMsg { + position: absolute; + top: -.6em; + display: inline-block; + padding: 0 1em; + background: var(--MI_THEME-panel); + font-size: 0.8em; + color: var(--MI_THEME-fgOnPanel); + margin: 0; + left: 50%; + transform: translateX(-50%); +} +</style> diff --git a/packages/frontend/src/components/MkSignin.totp.vue b/packages/frontend/src/components/MkSignin.totp.vue new file mode 100644 index 0000000000..670b8057c2 --- /dev/null +++ b/packages/frontend/src/components/MkSignin.totp.vue @@ -0,0 +1,74 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<div :class="$style.wrapper"> + <div class="_gaps" :class="$style.root"> + <div class="_gaps_s"> + <div :class="$style.totpIcon"> + <i class="ti ti-key"></i> + </div> + <div :class="$style.totpDescription">{{ i18n.ts['2fa'] }}</div> + </div> + + <!-- totp入力 --> + <form class="_gaps_s" @submit.prevent="emit('totpSubmitted', token)"> + <MkInput v-model="token" type="text" :pattern="isBackupCode ? '^[A-Z0-9]{32}$' :'^[0-9]{6}$'" autocomplete="one-time-code" required autofocus :spellcheck="false" :inputmode="isBackupCode ? undefined : 'numeric'"> + <template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template> + <template #prefix><i v-if="isBackupCode" class="ti ti-key"></i><i v-else class="ti ti-123"></i></template> + <template #caption><button class="_textButton" type="button" @click="isBackupCode = !isBackupCode">{{ isBackupCode ? i18n.ts.useTotp : i18n.ts.useBackupCode }}</button></template> + </MkInput> + + <MkButton type="submit" large primary rounded style="margin: 0 auto;">{{ i18n.ts.continue }} <i class="ti ti-arrow-right"></i></MkButton> + </form> + </div> +</div> +</template> + +<script setup lang="ts"> +import { ref } from 'vue'; + +import { i18n } from '@/i18n.js'; + +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; + +const emit = defineEmits<{ + (ev: 'totpSubmitted', token: string): void; +}>(); + +const token = ref(''); +const isBackupCode = ref(false); +</script> + +<style lang="scss" module> +.wrapper { + display: flex; + align-items: center; + width: 100%; + min-height: 336px; + + > .root { + width: 100%; + } +} + +.totpIcon { + margin: 0 auto; + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + text-align: center; + height: 64px; + width: 64px; + font-size: 24px; + line-height: 64px; + border-radius: 50%; +} + +.totpDescription { + text-align: center; + font-size: 1.1em; +} +</style> diff --git a/packages/frontend/src/components/MkSignin.vue b/packages/frontend/src/components/MkSignin.vue index 82e0df8a01..4a6219071b 100644 --- a/packages/frontend/src/components/MkSignin.vue +++ b/packages/frontend/src/components/MkSignin.vue @@ -4,245 +4,290 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<form :class="{ signing, totpLogin }" @submit.prevent="onSubmit"> - <div class="_gaps_m"> - <div v-show="withAvatar" :class="$style.avatar" :style="{ backgroundImage: user ? `url('${user.avatarUrl}')` : undefined, marginBottom: message ? '1.5em' : undefined }"></div> - <MkInfo v-if="message"> - {{ message }} - </MkInfo> - <div v-if="openOnRemote" class="_gaps_m"> - <div class="_gaps_s"> - <MkButton type="button" rounded primary style="margin: 0 auto;" @click="openRemote(openOnRemote)"> - {{ i18n.ts.continueOnRemote }} <i class="ti ti-external-link"></i> - </MkButton> - <button type="button" class="_button" :class="$style.instanceManualSelectButton" @click="specifyHostAndOpenRemote(openOnRemote)"> - {{ i18n.ts.specifyServerHost }} - </button> - </div> - <div :class="$style.orHr"> - <p :class="$style.orMsg">{{ i18n.ts.or }}</p> - </div> - </div> - <div v-if="!totpLogin" class="normal-signin _gaps_m"> - <MkInput v-model="username" :placeholder="i18n.ts.username" type="text" pattern="^[a-zA-Z0-9_]+$" :spellcheck="false" autocomplete="username webauthn" autofocus required data-cy-signin-username @update:modelValue="onUsernameChange"> - <template #prefix>@</template> - <template #suffix>@{{ host }}</template> - </MkInput> - <MkInput v-model="password" :placeholder="i18n.ts.password" type="password" autocomplete="current-password webauthn" :withPasswordToggle="true" required data-cy-signin-password> - <template #prefix><i class="ti ti-lock"></i></template> - <template #caption><button class="_textButton" type="button" @click="resetPassword">{{ i18n.ts.forgotPassword }}</button></template> - </MkInput> - <MkButton type="submit" large primary rounded :disabled="signing" style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> - </div> - <div v-if="totpLogin" class="2fa-signin" :class="{ securityKeys: user && user.securityKeys }"> - <div v-if="user && user.securityKeys" class="twofa-group tap-group"> - <p>{{ i18n.ts.useSecurityKey }}</p> - <MkButton v-if="!queryingKey" @click="query2FaKey"> - {{ i18n.ts.retry }} - </MkButton> - </div> - <div v-if="user && user.securityKeys" :class="$style.orHr"> - <p :class="$style.orMsg">{{ i18n.ts.or }}</p> - </div> - <div class="twofa-group totp-group _gaps"> - <MkInput v-model="token" type="text" :pattern="isBackupCode ? '^[A-Z0-9]{32}$' :'^[0-9]{6}$'" autocomplete="one-time-code" required :spellcheck="false" :inputmode="isBackupCode ? undefined : 'numeric'"> - <template #label>{{ i18n.ts.token }} ({{ i18n.ts['2fa'] }})</template> - <template #prefix><i v-if="isBackupCode" class="ti ti-key"></i><i v-else class="ti ti-123"></i></template> - <template #caption><button class="_textButton" type="button" @click="isBackupCode = !isBackupCode">{{ isBackupCode ? i18n.ts.useTotp : i18n.ts.useBackupCode }}</button></template> - </MkInput> - <MkButton type="submit" :disabled="signing" large primary rounded style="margin: 0 auto;">{{ signing ? i18n.ts.loggingIn : i18n.ts.login }}</MkButton> - </div> - </div> - <div v-if="!totpLogin && usePasswordLessLogin" :class="$style.orHr"> - <p :class="$style.orMsg">{{ i18n.ts.or }}</p> - </div> - <div v-if="!totpLogin && usePasswordLessLogin" class="twofa-group tap-group"> - <MkButton v-if="!queryingKey" type="submit" :disabled="signing" style="margin: auto auto;" rounded large primary @click="onPasskeyLogin"> - <i class="ti ti-device-usb" style="font-size: medium;"></i> - {{ signing ? i18n.ts.loggingIn : i18n.ts.signinWithPasskey }} - </MkButton> - <p v-if="queryingKey">{{ i18n.ts.useSecurityKey }}</p> - </div> +<div :class="$style.signinRoot"> + <Transition + mode="out-in" + :enterActiveClass="$style.transition_enterActive" + :leaveActiveClass="$style.transition_leaveActive" + :enterFromClass="$style.transition_enterFrom" + :leaveToClass="$style.transition_leaveTo" + + :inert="waiting" + > + <!-- 1. 外部サーãƒãƒ¼ã¸ã®è»¢é€ãƒ»username入力・パスã‚ー --> + <XInput + v-if="page === 'input'" + key="input" + :message="message" + :openOnRemote="openOnRemote" + + @usernameSubmitted="onUsernameSubmitted" + @passkeyClick="onPasskeyLogin" + /> + + <!-- 2. パスワード入力 --> + <XPassword + v-else-if="page === 'password'" + key="password" + ref="passwordPageEl" + + :user="userInfo!" + :needCaptcha="needCaptcha" + + @passwordSubmitted="onPasswordSubmitted" + /> + + <!-- 3. ワンタイムパスワード --> + <XTotp + v-else-if="page === 'totp'" + key="totp" + + @totpSubmitted="onTotpSubmitted" + /> + + <!-- 4. パスã‚ー --> + <XPasskey + v-else-if="page === 'passkey'" + key="passkey" + + :credentialRequest="credentialRequest!" + :isPerformingPasswordlessLogin="doingPasskeyFromInputPage" + + @done="onPasskeyDone" + @useTotp="onUseTotp" + /> + </Transition> + <div v-if="waiting" :class="$style.waitingRoot"> + <MkLoading/> </div> -</form> +</div> </template> -<script lang="ts" setup> -import { defineAsyncComponent, ref } from 'vue'; -import { toUnicode } from 'punycode/'; +<script setup lang="ts"> +import { nextTick, onBeforeUnmount, ref, shallowRef, useTemplateRef } from 'vue'; import * as Misskey from 'misskey-js'; -import { supported as webAuthnSupported, get as webAuthnRequest, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; -import { SigninWithPasskeyResponse } from 'misskey-js/entities.js'; -import { query, extractDomain } from '@@/js/url.js'; -import { host as configHost } from '@@/js/config.js'; -import MkDivider from './MkDivider.vue'; +import { supported as webAuthnSupported, parseRequestOptionsFromJSON } from '@github/webauthn-json/browser-ponyfill'; + +import type { AuthenticationPublicKeyCredential } from '@github/webauthn-json/browser-ponyfill'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; -import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; -import MkButton from '@/components/MkButton.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkInfo from '@/components/MkInfo.vue'; -import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; +import { showSuspendedDialog } from '@/scripts/show-suspended-dialog.js'; import { login } from '@/account.js'; import { i18n } from '@/i18n.js'; import { showSystemAccountDialog } from '@/scripts/show-system-account-dialog.js'; +import * as os from '@/os.js'; -const signing = ref(false); -const user = ref<Misskey.entities.UserDetailed | null>(null); -const usePasswordLessLogin = ref<Misskey.entities.UserDetailed['usePasswordLessLogin']>(true); -const username = ref(''); -const password = ref(''); -const token = ref(''); -const host = ref(toUnicode(configHost)); -const totpLogin = ref(false); -const isBackupCode = ref(false); -const queryingKey = ref(false); -let credentialRequest: CredentialRequestOptions | null = null; -const passkey_context = ref(''); +import XInput from '@/components/MkSignin.input.vue'; +import XPassword, { type PwResponse } from '@/components/MkSignin.password.vue'; +import XTotp from '@/components/MkSignin.totp.vue'; +import XPasskey from '@/components/MkSignin.passkey.vue'; const emit = defineEmits<{ - (ev: 'login', v: any): void; + (ev: 'login', v: Misskey.entities.SigninFlowResponse & { finished: true }): void; }>(); const props = withDefaults(defineProps<{ - withAvatar?: boolean; autoSet?: boolean; message?: string, openOnRemote?: OpenOnRemoteOptions, }>(), { - withAvatar: true, autoSet: false, message: '', openOnRemote: undefined, }); -function onUsernameChange(): void { - const usernameRequested = username.value; - misskeyApi('users/show', { - username: usernameRequested, - }).then(userResponse => { - if (userResponse.username === username.value) { - user.value = userResponse; - usePasswordLessLogin.value = userResponse.usePasswordLessLogin; - } - }, () => { - if (usernameRequested === username.value) { - user.value = null; - usePasswordLessLogin.value = true; - } - }); -} +const page = ref<'input' | 'password' | 'totp' | 'passkey'>('input'); +const waiting = ref(false); -function onLogin(res: any): Promise<void> | void { - if (props.autoSet) { - return login(res.i); - } -} +const passwordPageEl = useTemplateRef('passwordPageEl'); +const needCaptcha = ref(false); -async function query2FaKey(): Promise<void> { - if (credentialRequest == null) return; - queryingKey.value = true; - await webAuthnRequest(credentialRequest) - .catch(() => { - queryingKey.value = false; - return Promise.reject(null); - }).then(credential => { - credentialRequest = null; - queryingKey.value = false; - signing.value = true; - return misskeyApi('signin', { - username: username.value, - password: password.value, - credential: credential.toJSON(), - }); - }).then(res => { - emit('login', res); - return onLogin(res); - }).catch(err => { - if (err === null) return; - os.alert({ - type: 'error', - text: i18n.ts.signinFailed, - }); - signing.value = false; - }); -} +const userInfo = ref<null | Misskey.entities.UserDetailed>(null); +const password = ref(''); + +//#region Passkey Passwordless +const credentialRequest = shallowRef<CredentialRequestOptions | null>(null); +const passkeyContext = ref(''); +const doingPasskeyFromInputPage = ref(false); function onPasskeyLogin(): void { - signing.value = true; if (webAuthnSupported()) { + doingPasskeyFromInputPage.value = true; + waiting.value = true; misskeyApi('signin-with-passkey', {}) - .then((res: SigninWithPasskeyResponse) => { - totpLogin.value = false; - signing.value = false; - queryingKey.value = true; - passkey_context.value = res.context ?? ''; - credentialRequest = parseRequestOptionsFromJSON({ + .then((res) => { + passkeyContext.value = res.context ?? ''; + credentialRequest.value = parseRequestOptionsFromJSON({ publicKey: res.option, }); + + page.value = 'passkey'; + waiting.value = false; }) - .then(() => queryPasskey()) - .catch(loginFailed); + .catch(onSigninApiError); } } -async function queryPasskey(): Promise<void> { - if (credentialRequest == null) return; - queryingKey.value = true; - console.log('Waiting passkey auth...'); - await webAuthnRequest(credentialRequest) - .catch((err) => { - console.warn('Passkey Auth fail!: ', err); - queryingKey.value = false; - return Promise.reject(null); - }).then(credential => { - credentialRequest = null; - queryingKey.value = false; - signing.value = true; - return misskeyApi('signin-with-passkey', { - credential: credential.toJSON(), - context: passkey_context.value, - }); - }).then((res: SigninWithPasskeyResponse) => { +function onPasskeyDone(credential: AuthenticationPublicKeyCredential): void { + waiting.value = true; + + if (doingPasskeyFromInputPage.value) { + misskeyApi('signin-with-passkey', { + credential: credential.toJSON(), + context: passkeyContext.value, + }).then((res) => { + if (res.signinResponse == null) { + onSigninApiError(); + return; + } emit('login', res.signinResponse); - return onLogin(res.signinResponse); + }).catch(onSigninApiError); + } else if (userInfo.value != null) { + tryLogin({ + username: userInfo.value.username, + password: password.value, + credential: credential.toJSON(), }); + } } -function onSubmit(): void { - signing.value = true; - if (!totpLogin.value && user.value && user.value.twoFactorEnabled) { - if (webAuthnSupported() && user.value.securityKeys) { - misskeyApi('signin', { - username: username.value, - password: password.value, - }).then(res => { - totpLogin.value = true; - signing.value = false; - credentialRequest = parseRequestOptionsFromJSON({ - publicKey: res, - }); - }) - .then(() => query2FaKey()) - .catch(loginFailed); - } else { - totpLogin.value = true; - signing.value = false; - } +function onUseTotp(): void { + page.value = 'totp'; +} +//#endregion + +async function onUsernameSubmitted(username: string) { + waiting.value = true; + + userInfo.value = await misskeyApi('users/show', { + username, + }).catch(() => null); + + await tryLogin({ + username, + }); +} + +async function onPasswordSubmitted(pw: PwResponse) { + waiting.value = true; + password.value = pw.password; + + if (userInfo.value == null) { + await os.alert({ + type: 'error', + title: i18n.ts.noSuchUser, + text: i18n.ts.signinFailed, + }); + waiting.value = false; + return; + } else { + await tryLogin({ + username: userInfo.value.username, + password: pw.password, + 'hcaptcha-response': pw.captcha.hCaptchaResponse, + 'm-captcha-response': pw.captcha.mCaptchaResponse, + 'g-recaptcha-response': pw.captcha.reCaptchaResponse, + 'frc-captcha-solution': pw.captcha.fcResponse, + 'turnstile-response': pw.captcha.turnstileResponse, + 'testcaptcha-response': pw.captcha.testcaptchaResponse, + }); + } +} + +async function onTotpSubmitted(token: string) { + waiting.value = true; + + if (userInfo.value == null) { + await os.alert({ + type: 'error', + title: i18n.ts.noSuchUser, + text: i18n.ts.signinFailed, + }); + waiting.value = false; + return; } else { - misskeyApi('signin', { - username: username.value, + await tryLogin({ + username: userInfo.value.username, password: password.value, - token: user.value?.twoFactorEnabled ? token.value : undefined, - }).then(res => { + token, + }); + } +} + +async function tryLogin(req: Partial<Misskey.entities.SigninFlowRequest>): Promise<Misskey.entities.SigninFlowResponse> { + const _req = { + username: req.username ?? userInfo.value?.username, + ...req, + }; + + function assertIsSigninFlowRequest(x: Partial<Misskey.entities.SigninFlowRequest>): x is Misskey.entities.SigninFlowRequest { + return x.username != null; + } + + if (!assertIsSigninFlowRequest(_req)) { + throw new Error('Invalid request'); + } + + return await misskeyApi('signin-flow', _req).then(async (res) => { + if (res.finished) { emit('login', res); - onLogin(res); - }).catch(loginFailed); + await onLoginSucceeded(res); + } else { + switch (res.next) { + case 'captcha': { + needCaptcha.value = true; + page.value = 'password'; + break; + } + case 'password': { + needCaptcha.value = false; + page.value = 'password'; + break; + } + case 'totp': { + page.value = 'totp'; + break; + } + case 'passkey': { + if (webAuthnSupported()) { + credentialRequest.value = parseRequestOptionsFromJSON({ + publicKey: res.authRequest, + }); + page.value = 'passkey'; + } else { + page.value = 'totp'; + } + break; + } + } + + if (doingPasskeyFromInputPage.value === true) { + doingPasskeyFromInputPage.value = false; + page.value = 'input'; + password.value = ''; + } + passwordPageEl.value?.resetCaptcha(); + nextTick(() => { + waiting.value = false; + }); + } + return res; + }).catch((err) => { + onSigninApiError(err); + return Promise.reject(err); + }); +} + +async function onLoginSucceeded(res: Misskey.entities.SigninFlowResponse & { finished: true }) { + if (props.autoSet) { + await login(res.i); } } -function loginFailed(err: any): void { - switch (err.id) { +function onSigninApiError(err?: any): void { + const id = err?.id ?? null; + + switch (id) { case '6cc579cc-885d-43d8-95c2-b8c7fc963280': { os.alert({ type: 'error', @@ -275,6 +320,14 @@ function loginFailed(err: any): void { }); break; } + case 'cdf1235b-ac71-46d4-a3a6-84ccce48df6f': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.incorrectTotp, + }); + break; + } case '36b96a7d-b547-412d-aeed-2d611cdc8cdc': { os.alert({ type: 'error', @@ -283,6 +336,14 @@ function loginFailed(err: any): void { }); break; } + case '93b86c4b-72f9-40eb-9815-798928603d1e': { + os.alert({ + type: 'error', + title: i18n.ts.loginFailed, + text: i18n.ts.passkeyVerificationFailed, + }); + break; + } case 'b18c89a7-5b5e-4cec-bb5b-0419f332d430': { os.alert({ type: 'error', @@ -309,113 +370,55 @@ function loginFailed(err: any): void { } } - totpLogin.value = false; - signing.value = false; -} - -function resetPassword(): void { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkForgotPassword.vue')), {}, { - closed: () => dispose(), - }); -} - -function openRemote(options: OpenOnRemoteOptions, targetHost?: string): void { - switch (options.type) { - case 'web': - case 'lookup': { - let _path: string; - - if (options.type === 'lookup') { - // TODO: v2024.7.0以é™ãŒæµ¸é€ã—ã¦ããŸã‚‰æ£å¼ãªURLã«å¤‰æ›´ã™ã‚‹â–¼ - // _path = `/lookup?uri=${encodeURIComponent(_path)}`; - _path = `/authorize-follow?acct=${encodeURIComponent(options.url)}`; - } else { - _path = options.path; - } - - if (targetHost) { - window.open(`https://${targetHost}${_path}`, '_blank', 'noopener'); - } else { - window.open(`https://misskey-hub.net/mi-web/?path=${encodeURIComponent(_path)}`, '_blank', 'noopener'); - } - break; - } - case 'share': { - const params = query(options.params); - if (targetHost) { - window.open(`https://${targetHost}/share?${params}`, '_blank', 'noopener'); - } else { - window.open(`https://misskey-hub.net/share/?${params}`, '_blank', 'noopener'); - } - break; - } + if (doingPasskeyFromInputPage.value === true) { + doingPasskeyFromInputPage.value = false; + page.value = 'input'; + password.value = ''; } -} - -async function specifyHostAndOpenRemote(options: OpenOnRemoteOptions): Promise<void> { - const { canceled, result: hostTemp } = await os.inputText({ - title: i18n.ts.inputHostName, - placeholder: 'misskey.example.com', + passwordPageEl.value?.resetCaptcha(); + nextTick(() => { + waiting.value = false; }); - - if (canceled) return; - - let targetHost: string | null = hostTemp; - - // ドメイン部分ã ã‘ã‚’å–り出㙠- targetHost = extractDomain(targetHost); - if (targetHost == null) { - os.alert({ - type: 'error', - title: i18n.ts.invalidValue, - text: i18n.ts.tryAgain, - }); - return; - } - openRemote(options, targetHost); } + +onBeforeUnmount(() => { + password.value = ''; + needCaptcha.value = false; + userInfo.value = null; +}); </script> <style lang="scss" module> -.avatar { - margin: 0 auto 0 auto; - width: 64px; - height: 64px; - background: #ddd; - background-position: center; - background-size: cover; - border-radius: var(--radius-full); +.transition_enterActive, +.transition_leaveActive { + transition: opacity 0.3s cubic-bezier(0,0,.35,1), transform 0.3s cubic-bezier(0,0,.35,1); } - -.instanceManualSelectButton { - display: block; - text-align: center; - opacity: .7; - font-size: .8em; - - &:hover { - text-decoration: underline; - } +.transition_enterFrom { + opacity: 0; + transform: translateX(50px); +} +.transition_leaveTo { + opacity: 0; + transform: translateX(-50px); } -.orHr { +.signinRoot { + overflow-x: hidden; + overflow-x: clip; + position: relative; - margin: .4em auto; - width: 100%; - height: 1px; - background: var(--divider); } -.orMsg { +.waitingRoot { position: absolute; - top: -.6em; - display: inline-block; - padding: 0 1em; - background: var(--panel); - font-size: 0.8em; - color: var(--fgOnPanel); - margin: 0; - left: 50%; - transform: translateX(-50%); + top: 0; + left: 0; + width: 100%; + height: 100%; + background-color: color-mix(in srgb, var(--MI_THEME-panel), transparent 50%); + display: flex; + justify-content: center; + align-items: center; + z-index: 1; } </style> diff --git a/packages/frontend/src/components/MkSigninDialog.vue b/packages/frontend/src/components/MkSigninDialog.vue index d48780e9de..676a336ec7 100644 --- a/packages/frontend/src/components/MkSigninDialog.vue +++ b/packages/frontend/src/components/MkSigninDialog.vue @@ -4,26 +4,30 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkModalWindow - ref="dialog" - :width="400" - :height="450" - @close="onClose" +<MkModal + ref="modal" + :preferType="'dialog'" + @click="onClose" @closed="emit('closed')" > - <template #header>{{ i18n.ts.login }}</template> - - <MkSpacer :marginMin="20" :marginMax="28"> - <MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/> - </MkSpacer> -</MkModalWindow> + <div :class="$style.root"> + <div :class="$style.header"> + <div :class="$style.headerText"><i class="ti ti-login-2"></i> {{ i18n.ts.login }}</div> + <button :class="$style.closeButton" class="_button" @click="onClose"><i class="ti ti-x"></i></button> + </div> + <div :class="$style.content"> + <MkSignin :autoSet="autoSet" :message="message" :openOnRemote="openOnRemote" @login="onLogin"/> + </div> + </div> +</MkModal> </template> <script lang="ts" setup> +import * as Misskey from 'misskey-js'; import { shallowRef } from 'vue'; import type { OpenOnRemoteOptions } from '@/scripts/please-login.js'; import MkSignin from '@/components/MkSignin.vue'; -import MkModalWindow from '@/components/MkModalWindow.vue'; +import MkModal from '@/components/MkModal.vue'; import { i18n } from '@/i18n.js'; withDefaults(defineProps<{ @@ -37,20 +41,67 @@ withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'done', v: any): void; + (ev: 'done', v: Misskey.entities.SigninFlowResponse & { finished: true }): void; (ev: 'closed'): void; (ev: 'cancelled'): void; }>(); -const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); +const modal = shallowRef<InstanceType<typeof MkModal>>(); function onClose() { emit('cancelled'); - if (dialog.value) dialog.value.close(); + if (modal.value) modal.value.close(); } -function onLogin(res) { +function onLogin(res: Misskey.entities.SigninFlowResponse & { finished: true }) { emit('done', res); - if (dialog.value) dialog.value.close(); + if (modal.value) modal.value.close(); } </script> + +<style lang="scss" module> +.root { + overflow: auto; + margin: auto; + position: relative; + width: 100%; + max-width: 400px; + height: 100%; + max-height: 450px; + box-sizing: border-box; + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); +} + +.header { + position: sticky; + top: 0; + left: 0; + width: 100%; + height: 50px; + box-sizing: border-box; + display: flex; + align-items: center; + font-weight: bold; + backdrop-filter: var(--MI-blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + z-index: 1; +} + +.headerText { + padding: 0 20px; + box-sizing: border-box; +} + +.closeButton { + margin-left: auto; + padding: 16px; + font-size: 16px; + line-height: 16px; +} + +.content { + padding: 32px; + box-sizing: border-box; +} +</style> diff --git a/packages/frontend/src/components/MkSignupDialog.form.vue b/packages/frontend/src/components/MkSignupDialog.form.vue index 4c55831a3a..e636712389 100644 --- a/packages/frontend/src/components/MkSignupDialog.form.vue +++ b/packages/frontend/src/components/MkSignupDialog.form.vue @@ -21,12 +21,12 @@ SPDX-License-Identifier: AGPL-3.0-only <template #caption> <div><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.cannotBeChangedLater }}</div> <span v-if="usernameState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> - <span v-else-if="usernameState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> - <span v-else-if="usernameState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> - <span v-else-if="usernameState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> - <span v-else-if="usernameState === 'invalid-format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span> - <span v-else-if="usernameState === 'min-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span> - <span v-else-if="usernameState === 'max-range'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span> + <span v-else-if="usernameState === 'ok'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> + <span v-else-if="usernameState === 'unavailable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> + <span v-else-if="usernameState === 'error'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> + <span v-else-if="usernameState === 'invalid-format'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.usernameInvalidFormat }}</span> + <span v-else-if="usernameState === 'min-range'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooShort }}</span> + <span v-else-if="usernameState === 'max-range'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.tooLong }}</span> </template> </MkInput> <MkInput v-if="instance.emailRequiredForSignup" v-model="email" :debounce="true" type="email" :spellcheck="false" required data-cy-signup-email @update:modelValue="onChangeEmail"> @@ -34,32 +34,32 @@ SPDX-License-Identifier: AGPL-3.0-only <template #prefix><i class="ti ti-mail"></i></template> <template #caption> <span v-if="emailState === 'wait'" style="color:#999"><MkLoading :em="true"/> {{ i18n.ts.checking }}</span> - <span v-else-if="emailState === 'ok'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> - <span v-else-if="emailState === 'unavailable:used'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span> - <span v-else-if="emailState === 'unavailable:format'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span> - <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> - <span v-else-if="emailState === 'unavailable:banned'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.banned }}</span> - <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span> - <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> - <span v-else-if="emailState === 'unavailable'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> - <span v-else-if="emailState === 'error'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> + <span v-else-if="emailState === 'ok'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.available }}</span> + <span v-else-if="emailState === 'unavailable:used'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.used }}</span> + <span v-else-if="emailState === 'unavailable:format'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.format }}</span> + <span v-else-if="emailState === 'unavailable:disposable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.disposable }}</span> + <span v-else-if="emailState === 'unavailable:banned'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.banned }}</span> + <span v-else-if="emailState === 'unavailable:mx'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.mx }}</span> + <span v-else-if="emailState === 'unavailable:smtp'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts._emailUnavailable.smtp }}</span> + <span v-else-if="emailState === 'unavailable'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.unavailable }}</span> + <span v-else-if="emailState === 'error'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.error }}</span> </template> </MkInput> <MkInput v-model="password" type="password" autocomplete="new-password" required data-cy-signup-password @update:modelValue="onChangePassword"> <template #label>{{ i18n.ts.password }}</template> <template #prefix><i class="ti ti-lock"></i></template> <template #caption> - <span v-if="passwordStrength == 'low'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span> - <span v-if="passwordStrength == 'medium'" style="color: var(--warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span> - <span v-if="passwordStrength == 'high'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span> + <span v-if="passwordStrength == 'low'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.weakPassword }}</span> + <span v-if="passwordStrength == 'medium'" style="color: var(--MI_THEME-warn)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.normalPassword }}</span> + <span v-if="passwordStrength == 'high'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.strongPassword }}</span> </template> </MkInput> <MkInput v-model="retypedPassword" type="password" autocomplete="new-password" required data-cy-signup-password-retype @update:modelValue="onChangePasswordRetype"> <template #label>{{ i18n.ts.password }} ({{ i18n.ts.retype }})</template> <template #prefix><i class="ti ti-lock"></i></template> <template #caption> - <span v-if="passwordRetypeState == 'match'" style="color: var(--success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span> - <span v-if="passwordRetypeState == 'not-match'" style="color: var(--error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span> + <span v-if="passwordRetypeState == 'match'" style="color: var(--MI_THEME-success)"><i class="ti ti-check ti-fw"></i> {{ i18n.ts.passwordMatched }}</span> + <span v-if="passwordRetypeState == 'not-match'" style="color: var(--MI_THEME-error)"><i class="ti ti-alert-triangle ti-fw"></i> {{ i18n.ts.passwordNotMatched }}</span> </template> </MkInput> <MkInput v-if="instance.approvalRequiredForSignup" v-model="reason" type="text" :spellcheck="false" required data-cy-signup-reason> @@ -71,6 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkCaptcha v-if="instance.enableRecaptcha" ref="recaptcha" v-model="reCaptchaResponse" :class="$style.captcha" provider="recaptcha" :sitekey="instance.recaptchaSiteKey"/> <MkCaptcha v-if="instance.enableTurnstile" ref="turnstile" v-model="turnstileResponse" :class="$style.captcha" provider="turnstile" :sitekey="instance.turnstileSiteKey"/> <MkCaptcha v-if="instance.enableFC" ref="fc" v-model="fcResponse" :class="$style.captcha" provider="fc" :sitekey="instance.fcSiteKey"/> + <MkCaptcha v-if="instance.enableTestcaptcha" ref="testcaptcha" v-model="testcaptchaResponse" :class="$style.captcha" provider="testcaptcha"/> <MkButton type="submit" :disabled="shouldDisableSubmitting" large gradate rounded data-cy-signup-submit style="margin: 0 auto;"> <template v-if="submitting"> <MkLoading :em="true" :colored="false"/> @@ -86,10 +87,10 @@ SPDX-License-Identifier: AGPL-3.0-only import { ref, computed } from 'vue'; import { toUnicode } from 'punycode/'; import * as Misskey from 'misskey-js'; +import * as config from '@@/js/config.js'; import MkButton from './MkButton.vue'; import MkInput from './MkInput.vue'; import MkCaptcha, { type Captcha } from '@/components/MkCaptcha.vue'; -import * as config from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { login } from '@/account.js'; @@ -103,7 +104,7 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'signup', user: Misskey.entities.SigninResponse): void; + (ev: 'signup', user: Misskey.entities.SignupResponse): void; (ev: 'signupEmailPending'): void; (ev: 'approvalPending'): void; }>(); @@ -111,9 +112,11 @@ const emit = defineEmits<{ const host = toUnicode(config.host); const hcaptcha = ref<Captcha | undefined>(); +const mcaptcha = ref<Captcha | undefined>(); const recaptcha = ref<Captcha | undefined>(); const turnstile = ref<Captcha | undefined>(); const fc = ref<Captcha | undefined>(); +const testcaptcha = ref<Captcha | undefined>(); const username = ref<string>(''); const password = ref<string>(''); @@ -131,6 +134,7 @@ const mCaptchaResponse = ref<string | null>(null); const reCaptchaResponse = ref<string | null>(null); const turnstileResponse = ref<string | null>(null); const fcResponse = ref<string | null>(null); +const testcaptchaResponse = ref<string | null>(null); const usernameAbortController = ref<null | AbortController>(null); const emailAbortController = ref<null | AbortController>(null); @@ -141,6 +145,7 @@ const shouldDisableSubmitting = computed((): boolean => { instance.enableRecaptcha && !reCaptchaResponse.value || instance.enableTurnstile && !turnstileResponse.value || instance.enableFC && !fcResponse.value || + instance.enableTestcaptcha && !testcaptchaResponse.value || instance.emailRequiredForSignup && emailState.value !== 'ok' || usernameState.value !== 'ok' || passwordRetypeState.value !== 'match'; @@ -259,20 +264,33 @@ async function onSubmit(): Promise<void> { if (submitting.value) return; submitting.value = true; - try { - await misskeyApi('signup', { - username: username.value, - password: password.value, - emailAddress: email.value, - invitationCode: invitationCode.value, - reason: reason.value, - 'hcaptcha-response': hCaptchaResponse.value, - 'm-captcha-response': mCaptchaResponse.value, - 'g-recaptcha-response': reCaptchaResponse.value, - 'turnstile-response': turnstileResponse.value, - 'frc-captcha-solution': fcResponse.value, - }); - if (instance.emailRequiredForSignup) { + const signupPayload: Misskey.entities.SignupRequest = { + username: username.value, + password: password.value, + emailAddress: email.value, + invitationCode: invitationCode.value, + reason: reason.value, + 'hcaptcha-response': hCaptchaResponse.value, + 'm-captcha-response': mCaptchaResponse.value, + 'g-recaptcha-response': reCaptchaResponse.value, + 'turnstile-response': turnstileResponse.value, + 'frc-captcha-solution': fcResponse.value, + 'testcaptcha-response': testcaptchaResponse.value, + }; + + const res = await fetch(`${config.apiUrl}/signup`, { + method: 'POST', + headers: { + 'Content-Type': 'application/json', + }, + body: JSON.stringify(signupPayload), + }).catch(() => { + onSignupApiError(); + return null; + }); + + if (res && res.ok) { + if (res.status === 204 || instance.emailRequiredForSignup) { os.alert({ type: 'success', title: i18n.ts._signup.almostThere, @@ -287,28 +305,35 @@ async function onSubmit(): Promise<void> { }); emit('approvalPending'); } else { - const res = await misskeyApi('signin', { - username: username.value, - password: password.value, - }); - emit('signup', res); + const resJson = (await res.json()) as Misskey.entities.SignupResponse; + if (_DEV_) console.log(resJson); + + emit('signup', resJson); if (props.autoSet) { - return login(res.i); + await login(resJson.token); } } - } catch { - submitting.value = false; - hcaptcha.value?.reset?.(); - recaptcha.value?.reset?.(); - turnstile.value?.reset?.(); - fc.value?.reset?.(); - - os.alert({ - type: 'error', - text: i18n.ts.somethingHappened, - }); + } else { + onSignupApiError(); } + + submitting.value = false; +} + +function onSignupApiError() { + submitting.value = false; + hcaptcha.value?.reset?.(); + mcaptcha.value?.reset?.(); + recaptcha.value?.reset?.(); + turnstile.value?.reset?.(); + fc.value?.reset?.(); + testcaptcha.value?.reset?.(); + + os.alert({ + type: 'error', + text: i18n.ts.somethingHappened, + }); } </script> @@ -317,8 +342,8 @@ async function onSubmit(): Promise<void> { padding: 16px; text-align: center; font-size: 26px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } .captcha { diff --git a/packages/frontend/src/components/MkSignupDialog.rules.vue b/packages/frontend/src/components/MkSignupDialog.rules.vue index 251c805401..06481b808c 100644 --- a/packages/frontend/src/components/MkSignupDialog.rules.vue +++ b/packages/frontend/src/components/MkSignupDialog.rules.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder v-if="availableServerRules" :defaultOpen="true"> <template #label>{{ i18n.ts.serverRules }}</template> - <template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--success)"></i></template> + <template #suffix><i v-if="agreeServerRules" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template> <ol class="_gaps_s" :class="$style.rules"> <li v-for="item in instance.serverRules" :class="$style.rule"><div :class="$style.ruleText" v-html="sanitizeHtml(item)"></div></li> @@ -32,7 +32,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder v-if="availableTos || availablePrivacyPolicy" :defaultOpen="true"> <template #label>{{ tosPrivacyPolicyLabel }}</template> - <template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--success)"></i></template> + <template #suffix><i v-if="agreeTosAndPrivacyPolicy" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template> <div class="_gaps_s"> <div v-if="availableTos"><a :href="instance.tosUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.termsOfService }} <i class="ti ti-external-link"></i></a></div> <div v-if="availablePrivacyPolicy"><a :href="instance.privacyPolicyUrl ?? undefined" class="_link" target="_blank">{{ i18n.ts.privacyPolicy }} <i class="ti ti-external-link"></i></a></div> @@ -43,7 +43,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder :defaultOpen="true"> <template #label>{{ i18n.ts.basicNotesBeforeCreateAccount }}</template> - <template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--success)"></i></template> + <template #suffix><i v-if="agreeNote" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template> <a href="https://activitypub.software/TransFem-org/Sharkey/-/blob/stable/IMPORTANT_NOTES.md" class="_link" target="_blank">{{ i18n.ts.basicNotesBeforeCreateAccount }} <i class="ti ti-external-link"></i></a> @@ -151,8 +151,8 @@ async function updateAgreeNote(v: boolean) { padding: 16px; text-align: center; font-size: 26px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } .rules { @@ -171,19 +171,19 @@ async function updateAgreeNote(v: boolean) { flex-shrink: 0; display: flex; position: sticky; - top: calc(var(--stickyTop, 0px) + 8px); + top: calc(var(--MI-stickyTop, 0px) + 8px); counter-increment: item; content: counter(item); width: 32px; height: 32px; line-height: 32px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); font-size: 13px; font-weight: bold; align-items: center; justify-content: center; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } } diff --git a/packages/frontend/src/components/MkSignupDialog.vue b/packages/frontend/src/components/MkSignupDialog.vue index 91e7d5dd53..291c3ecc2f 100644 --- a/packages/frontend/src/components/MkSignupDialog.vue +++ b/packages/frontend/src/components/MkSignupDialog.vue @@ -8,8 +8,8 @@ SPDX-License-Identifier: AGPL-3.0-only ref="dialog" :width="500" :height="600" - @close="dialog?.close()" - @closed="$emit('closed')" + @close="onClose" + @closed="emit('closed')" > <template #header>{{ i18n.ts.signup }}</template> @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only :leaveToClass="$style.transition_x_leaveTo" > <template v-if="!isAcceptedServerRule"> - <XServerRules @done="isAcceptedServerRule = true" @cancel="dialog?.close()"/> + <XServerRules @done="isAcceptedServerRule = true" @cancel="onClose"/> </template> <template v-else> <XSignup :autoSet="autoSet" @signup="onSignup" @signupEmailPending="onSignupEmailPending" @approvalPending="onApprovalPending"/> @@ -47,7 +47,8 @@ const props = withDefaults(defineProps<{ }); const emit = defineEmits<{ - (ev: 'done', res: Misskey.entities.SigninResponse): void; + (ev: 'done', res: Misskey.entities.SignupResponse): void; + (ev: 'cancelled'): void; (ev: 'closed'): void; }>(); @@ -55,7 +56,12 @@ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const isAcceptedServerRule = ref(false); -function onSignup(res: Misskey.entities.SigninResponse) { +function onClose() { + emit('cancelled'); + dialog.value?.close(); +} + +function onSignup(res: Misskey.entities.SignupResponse) { emit('done', res); dialog.value?.close(); } diff --git a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue index 7743a89242..84dc244b23 100644 --- a/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue +++ b/packages/frontend/src/components/MkSourceCodeAvailablePopup.vue @@ -63,22 +63,22 @@ function close() { .root { position: fixed; z-index: v-bind(zIndex); - bottom: var(--margin); + bottom: var(--MI-margin); left: 0; right: 0; margin: auto; box-sizing: border-box; - width: calc(100% - (var(--margin) * 2)); + width: calc(100% - (var(--MI-margin) * 2)); max-width: 500px; display: flex; - backdrop-filter: var(--blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .icon { text-align: center; padding-top: 25px; width: 100px; - color: var(--accent); + color: var(--MI_THEME-accent); } @media (max-width: 500px) { .icon { diff --git a/packages/frontend/src/components/MkSubNoteContent.vue b/packages/frontend/src/components/MkSubNoteContent.vue index 6bd00fcc2a..a32fd53c51 100644 --- a/packages/frontend/src/components/MkSubNoteContent.vue +++ b/packages/frontend/src/components/MkSubNoteContent.vue @@ -110,20 +110,20 @@ watch(() => props.expandAllCws, (expandAllCws) => { left: 0; width: 100%; height: 64px; - // background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + // background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); > .fadeLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } &:hover { > .fadeLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } } @@ -132,18 +132,18 @@ watch(() => props.expandAllCws, (expandAllCws) => { .reply { margin-right: 6px; - color: var(--accent); + color: var(--MI_THEME-accent); } .rp { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -152,7 +152,7 @@ watch(() => props.expandAllCws, (expandAllCws) => { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) - 100px); + bottom: calc(var(--MI-stickyBottom, 0px) - 100px); } .playMFMButton { @@ -161,10 +161,10 @@ watch(() => props.expandAllCws, (expandAllCws) => { .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } diff --git a/packages/frontend/src/components/MkSuperMenu.vue b/packages/frontend/src/components/MkSuperMenu.vue index 430e3c7958..c9c173aa35 100644 --- a/packages/frontend/src/components/MkSuperMenu.vue +++ b/packages/frontend/src/components/MkSuperMenu.vue @@ -28,11 +28,38 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> -<script lang="ts" setup> -import { } from 'vue'; +<script lang="ts"> +export type SuperMenuDef = { + title?: string; + items: ({ + type: 'a'; + href: string; + target?: string; + icon?: string; + text: string; + danger?: boolean; + active?: boolean; + } | { + type: 'button'; + icon?: string; + text: string; + danger?: boolean; + active?: boolean; + action: (ev: MouseEvent) => void; + } | { + type: 'link'; + to: string; + icon?: string; + text: string; + danger?: boolean; + active?: boolean; + })[]; +}; +</script> +<script lang="ts" setup> defineProps<{ - def: any[]; + def: SuperMenuDef[]; grid?: boolean; }>(); </script> @@ -43,7 +70,7 @@ defineProps<{ & + .group { margin-top: 16px; padding-top: 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > .title { @@ -59,12 +86,12 @@ defineProps<{ width: 100%; box-sizing: border-box; padding: 9px 16px 9px 8px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); font-size: 0.9em; &:hover { text-decoration: none; - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } &:focus-visible { @@ -72,12 +99,12 @@ defineProps<{ } &.active { - color: var(--accent); - background: var(--accentedBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-accentedBg); } &.danger { - color: var(--error); + color: var(--MI_THEME-error); } > .icon { @@ -128,10 +155,10 @@ defineProps<{ &:hover { text-decoration: none; background: none; - color: var(--accent); + color: var(--MI_THEME-accent); > .icon { - background: var(--accentedBg); + background: var(--MI_THEME-accentedBg); } } @@ -144,8 +171,8 @@ defineProps<{ width: 60px; height: 60px; aspect-ratio: 1; - background: var(--panel); - border-radius: var(--radius-full); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-full); } > .text { diff --git a/packages/frontend/src/components/MkSwitch.button.vue b/packages/frontend/src/components/MkSwitch.button.vue index f7c413e1d3..581aa4e644 100644 --- a/packages/frontend/src/components/MkSwitch.button.vue +++ b/packages/frontend/src/components/MkSwitch.button.vue @@ -51,18 +51,18 @@ const toggle = () => { width: calc(var(--height) * 1.6); height: calc(var(--height) + 2px); // æž ç·š outline: none; - background: var(--switchOffBg); + background: var(--MI_THEME-switchOffBg); background-clip: content-box; - border: solid 1px var(--switchOffBg); - border-radius: var(--radius-ellipse); + border: solid 1px var(--MI_THEME-switchOffBg); + border-radius: var(--MI-radius-ellipse); cursor: pointer; transition: inherit; user-select: none; } .buttonChecked { - background-color: var(--switchOnBg) !important; - border-color: var(--switchOnBg) !important; + background-color: var(--MI_THEME-switchOnBg) !important; + border-color: var(--MI_THEME-switchOnBg) !important; } .buttonDisabled { @@ -75,17 +75,17 @@ const toggle = () => { top: 3px; width: calc(var(--height) - 6px); height: calc(var(--height) - 6px); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); transition: all 0.2s ease; &:not(.knobChecked) { left: 3px; - background: var(--switchOffFg); + background: var(--MI_THEME-switchOffFg); } } .knobChecked { left: calc(calc(100% - var(--height)) + 3px); - background: var(--switchOnFg); + background: var(--MI_THEME-switchOnFg); } </style> diff --git a/packages/frontend/src/components/MkSwitch.vue b/packages/frontend/src/components/MkSwitch.vue index a0994d9cc9..5e6029ee40 100644 --- a/packages/frontend/src/components/MkSwitch.vue +++ b/packages/frontend/src/components/MkSwitch.vue @@ -59,7 +59,7 @@ const toggle = () => { &:hover { > .button { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } @@ -77,7 +77,7 @@ const toggle = () => { margin: 0; &:focus-visible ~ .toggle { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: 2px; } } @@ -87,7 +87,7 @@ const toggle = () => { margin-top: 2px; display: block; transition: inherit; - color: var(--fg); + color: var(--MI_THEME-fg); } .label { @@ -99,7 +99,7 @@ const toggle = () => { .caption { margin: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: 0.85em; &:empty { diff --git a/packages/frontend/src/components/MkSystemWebhookEditor.vue b/packages/frontend/src/components/MkSystemWebhookEditor.vue index ec3b1c90ca..485d003f93 100644 --- a/packages/frontend/src/components/MkSystemWebhookEditor.vue +++ b/packages/frontend/src/components/MkSystemWebhookEditor.vue @@ -55,6 +55,18 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.userCreated)" @click="test('userCreated')"><i class="ti ti-send"></i></MkButton> </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="events.inactiveModeratorsWarning" :disabled="disabledEvents.inactiveModeratorsWarning"> + <template #label>{{ i18n.ts._webhookSettings._systemEvents.inactiveModeratorsWarning }}</template> + </MkSwitch> + <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.inactiveModeratorsWarning)" @click="test('inactiveModeratorsWarning')"><i class="ti ti-send"></i></MkButton> + </div> + <div :class="$style.switchBox"> + <MkSwitch v-model="events.inactiveModeratorsInvitationOnlyChanged" :disabled="disabledEvents.inactiveModeratorsInvitationOnlyChanged"> + <template #label>{{ i18n.ts._webhookSettings._systemEvents.inactiveModeratorsInvitationOnlyChanged }}</template> + </MkSwitch> + <MkButton v-show="mode === 'edit'" transparent :class="$style.testButton" :disabled="!(isActive && events.inactiveModeratorsInvitationOnlyChanged)" @click="test('inactiveModeratorsInvitationOnlyChanged')"><i class="ti ti-send"></i></MkButton> + </div> </div> <div v-show="mode === 'edit'" :class="$style.description"> @@ -100,6 +112,8 @@ type EventType = { abuseReport: boolean; abuseReportResolved: boolean; userCreated: boolean; + inactiveModeratorsWarning: boolean; + inactiveModeratorsInvitationOnlyChanged: boolean; } const emit = defineEmits<{ @@ -123,6 +137,8 @@ const events = ref<EventType>({ abuseReport: true, abuseReportResolved: true, userCreated: true, + inactiveModeratorsWarning: true, + inactiveModeratorsInvitationOnlyChanged: true, }); const isActive = ref<boolean>(true); @@ -130,6 +146,8 @@ const disabledEvents = ref<EventType>({ abuseReport: false, abuseReportResolved: false, userCreated: false, + inactiveModeratorsWarning: false, + inactiveModeratorsInvitationOnlyChanged: false, }); const disableSubmitButton = computed(() => { @@ -261,10 +279,10 @@ onMounted(async () => { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .switchBox { @@ -289,6 +307,6 @@ onMounted(async () => { .description { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/components/MkTab.vue b/packages/frontend/src/components/MkTab.vue index 54ab8fc663..52e4d304bc 100644 --- a/packages/frontend/src/components/MkTab.vue +++ b/packages/frontend/src/components/MkTab.vue @@ -39,7 +39,7 @@ export default defineComponent({ > button { flex: 1; padding: 10px 8px; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); &:disabled { opacity: 1 !important; @@ -47,13 +47,13 @@ export default defineComponent({ } &.active { - color: var(--accent); - background: var(--accentedBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-accentedBg); } &:not(.active):hover { - color: var(--fgHighlighted); - background: var(--panelHighlight); + color: var(--MI_THEME-fgHighlighted); + background: var(--MI_THEME-panelHighlight); } &:not(:first-child) { diff --git a/packages/frontend/src/components/MkTagCloud.vue b/packages/frontend/src/components/MkTagCloud.vue index 6b9c181597..87aa046963 100644 --- a/packages/frontend/src/components/MkTagCloud.vue +++ b/packages/frontend/src/components/MkTagCloud.vue @@ -33,7 +33,7 @@ watch(available, () => { try { window.TagCanvas.Start(idForCanvas, idForTags, { textColour: '#ffffff', - outlineColour: tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(), + outlineColour: tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(), outlineRadius: 10, initial: [-0.030, -0.010], frontSelect: true, diff --git a/packages/frontend/src/components/MkTextarea.vue b/packages/frontend/src/components/MkTextarea.vue index 72d6e12656..9deb6528d1 100644 --- a/packages/frontend/src/components/MkTextarea.vue +++ b/packages/frontend/src/components/MkTextarea.vue @@ -159,7 +159,7 @@ onUnmounted(() => { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; @@ -179,23 +179,23 @@ onUnmounted(() => { font: inherit; font-weight: normal; font-size: 1em; - color: var(--fg); - background: var(--panel); - border: solid 1px var(--panel); - border-radius: var(--radius-sm); + color: var(--MI_THEME-fg); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); outline: none; box-shadow: none; box-sizing: border-box; transition: border-color 0.1s ease-out; &:hover { - border-color: var(--inputBorderHover) !important; + border-color: var(--MI_THEME-inputBorderHover) !important; } } .focused { > .textarea { - border-color: var(--accent) !important; + border-color: var(--MI_THEME-accent) !important; } } @@ -226,7 +226,7 @@ onUnmounted(() => { .mfmPreview { padding: 12px; - border-radius: var(--radius); + border-radius: var(--MI-radius); box-sizing: border-box; min-height: 130px; pointer-events: none; diff --git a/packages/frontend/src/components/MkTimeline.vue b/packages/frontend/src/components/MkTimeline.vue index b69c19eb9e..7a9abab62e 100644 --- a/packages/frontend/src/components/MkTimeline.vue +++ b/packages/frontend/src/components/MkTimeline.vue @@ -39,10 +39,12 @@ const props = withDefaults(defineProps<{ withRenotes?: boolean; withReplies?: boolean; withBots?: boolean; + withSensitive?: boolean; onlyFiles?: boolean; }>(), { withRenotes: true, withReplies: false, + withSensitive: true, onlyFiles: false, withBots: true, }); @@ -53,6 +55,7 @@ const emit = defineEmits<{ }>(); provide('inTimeline', true); +provide('tl_withSensitive', computed(() => props.withSensitive)); provide('inChannel', computed(() => props.src === 'channel')); type TimelineQueryType = { @@ -275,6 +278,9 @@ function refreshEndpointAndChannel() { // IDãŒåˆ‡ã‚Šæ›¿ã‚ã£ãŸã‚‰åˆ‡ã‚Šæ›¿ãˆå…ˆã®TLを表示ã•ã›ãŸã„ watch(() => [props.list, props.antenna, props.channel, props.role, props.withRenotes], refreshEndpointAndChannel); +// withSensitiveã¯ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆã§å®Œçµã™ã‚‹å‡¦ç†ã®ãŸã‚ã€å˜ã«ãƒªãƒãƒ¼ãƒ‰ã™ã‚‹ã ã‘ã§OK +watch(() => props.withSensitive, reloadTimeline); + // åˆå›žè¡¨ç¤ºç”¨ refreshEndpointAndChannel(); diff --git a/packages/frontend/src/components/MkToast.vue b/packages/frontend/src/components/MkToast.vue index f731b3264f..38b537cbc9 100644 --- a/packages/frontend/src/components/MkToast.vue +++ b/packages/frontend/src/components/MkToast.vue @@ -70,7 +70,7 @@ onMounted(() => { max-width: calc(100% - 32px); width: min-content; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; text-align: center; pointer-events: none; diff --git a/packages/frontend/src/components/MkTokenGenerateWindow.vue b/packages/frontend/src/components/MkTokenGenerateWindow.vue index b32066c950..73aef68964 100644 --- a/packages/frontend/src/components/MkTokenGenerateWindow.vue +++ b/packages/frontend/src/components/MkTokenGenerateWindow.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only :okButtonDisabled="false" :canClose="false" @close="dialog?.close()" - @closed="$emit('closed')" + @closed="emit('closed')" @ok="ok()" > <template #header>{{ title || i18n.ts.generateAccessToken }}</template> @@ -136,15 +136,15 @@ function enableAll(): void { .adminPermissions { margin: 8px -6px 0; padding: 24px 6px 6px; - border: 2px solid var(--error); - border-radius: calc(var(--radius) / 2); + border: 2px solid var(--MI_THEME-error); + border-radius: calc(var(--MI-radius) / 2); } .adminPermissionsHeader { margin: -34px 0 6px 12px; padding: 0 4px; width: fit-content; - color: var(--error); - background: var(--panel); + color: var(--MI_THEME-error); + background: var(--MI_THEME-panel); } </style> diff --git a/packages/frontend/src/components/MkTooltip.vue b/packages/frontend/src/components/MkTooltip.vue index aac07008a4..22e74aa6d1 100644 --- a/packages/frontend/src/components/MkTooltip.vue +++ b/packages/frontend/src/components/MkTooltip.vue @@ -109,8 +109,8 @@ onUnmounted(() => { padding: 8px 12px; box-sizing: border-box; text-align: center; - border-radius: var(--radius-xs); - border: solid 0.5px var(--divider); + border-radius: var(--MI-radius-xs); + border: solid 0.5px var(--MI_THEME-divider); pointer-events: none; transform-origin: center center; } diff --git a/packages/frontend/src/components/MkTutorialDialog.Note.vue b/packages/frontend/src/components/MkTutorialDialog.Note.vue index cec7d69943..53b8db38b2 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Note.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Note.vue @@ -23,7 +23,7 @@ SPDX-License-Identifier: AGPL-3.0-only </I18n> <MkNote :class="$style.exampleNoteRoot" :note="exampleNote" :mock="true" @reaction="addReaction" @removeReaction="removeReaction"/> <div v-if="onceReacted"> - <b style="color: var(--accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br> + <b style="color: var(--MI_THEME-accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._reaction.reactNotification }}<br> <I18n :src="i18n.ts._initialTutorial._reaction.reactDone"> <template #undo> <i class="ph-minus ph-bold ph-lg"></i> @@ -116,13 +116,13 @@ function removeReaction(emoji) { <style lang="scss" module> .exampleNoteRoot { - border-radius: var(--radius); - border: var(--panelBorder); - background: var(--panel); + border-radius: var(--MI-radius); + border: var(--MI_THEME-panelBorder); + background: var(--MI_THEME-panel); } .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue index a9014d4202..367c573617 100644 --- a/packages/frontend/src/components/MkTutorialDialog.PostNote.vue +++ b/packages/frontend/src/components/MkTutorialDialog.PostNote.vue @@ -81,14 +81,14 @@ const exampleCWNote = reactive<Misskey.entities.Note>({ <style lang="scss" module> .exampleRoot { max-width: none!important; - border-radius: var(--radius); - border: var(--panelBorder); - background: var(--panel); + border-radius: var(--MI-radius); + border: var(--MI_THEME-panelBorder); + background: var(--MI_THEME-panel); } .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .image { @@ -101,7 +101,7 @@ const exampleCWNote = reactive<Misskey.entities.Note>({ display: block; width: 100%; height: 40px; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: bold; text-align: left; @@ -116,8 +116,8 @@ const exampleCWNote = reactive<Misskey.entities.Note>({ left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-ellipse); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } } diff --git a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue index 322082f5a0..e1fc3e4f26 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Sensitive.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only :initialNote="exampleNote" @fileChangeSensitive="doSucceeded" ></MkPostForm> - <div v-if="onceSucceeded"><b style="color: var(--accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._howToMakeAttachmentsSensitive.sensitiveSucceeded }}</div> + <div v-if="onceSucceeded"><b style="color: var(--MI_THEME-accent);"><i class="ti ti-check"></i> {{ i18n.ts._initialTutorial.wellDone }}</b> {{ i18n.ts._initialTutorial._howToMakeAttachmentsSensitive.sensitiveSucceeded }}</div> <MkFolder> <template #label>{{ i18n.ts.previewNoteText }}</template> <MkNote :mock="true" :note="exampleNote" :class="$style.exampleRoot"></MkNote> @@ -91,14 +91,14 @@ const exampleNote = reactive<Misskey.entities.Note>({ <style lang="scss" module> .exampleRoot { - border-radius: var(--radius); - border: var(--panelBorder); - background: var(--panel); + border-radius: var(--MI-radius); + border: var(--MI_THEME-panelBorder); + background: var(--MI_THEME-panel); } .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .image { @@ -111,7 +111,7 @@ const exampleNote = reactive<Misskey.entities.Note>({ display: block; width: 100%; height: 40px; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: bold; text-align: left; @@ -126,8 +126,8 @@ const exampleNote = reactive<Misskey.entities.Note>({ left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-ellipse); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } } diff --git a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue index b900a30c85..931b7343c5 100644 --- a/packages/frontend/src/components/MkTutorialDialog.Timeline.vue +++ b/packages/frontend/src/components/MkTutorialDialog.Timeline.vue @@ -31,14 +31,14 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js'; <style lang="scss" module> .exampleNoteRoot { - border-radius: var(--radius); - border: var(--panelBorder); - background: var(--panel); + border-radius: var(--MI-radius); + border: var(--MI_THEME-panelBorder); + background: var(--MI_THEME-panel); } .divider { height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } .image { @@ -51,7 +51,7 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js'; display: block; width: 100%; height: 40px; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: bold; text-align: left; @@ -66,8 +66,8 @@ import { basicTimelineIconClass, basicTimelineTypes } from '@/timelines.js'; left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-ellipse); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } } diff --git a/packages/frontend/src/components/MkTutorialDialog.vue b/packages/frontend/src/components/MkTutorialDialog.vue index 1f5a2b9381..11d7c8dc4d 100644 --- a/packages/frontend/src/components/MkTutorialDialog.vue +++ b/packages/frontend/src/components/MkTutorialDialog.vue @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> - <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialTutorial._landing.title }}</div> <div>{{ i18n.ts._initialTutorial._landing.description }}</div> <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" @click="page++">{{ i18n.ts._initialTutorial.launchTutorial }} <i class="ti ti-arrow-right"></i></MkButton> @@ -126,7 +126,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> - <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialTutorial._done.title }}</div> <I18n :src="i18n.ts._initialTutorial._done.description" tag="div" style="padding: 0 16px;"> <template #link> @@ -223,7 +223,7 @@ async function close(skip: boolean) { .progressBarValue { height: 100%; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); transition: all 0.5s cubic-bezier(0,.5,.5,1); } @@ -253,7 +253,7 @@ async function close(skip: boolean) { left: 0; flex-shrink: 0; padding: 12px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); -webkit-backdrop-filter: blur(15px); backdrop-filter: blur(15px); } diff --git a/packages/frontend/src/components/MkUpdated.vue b/packages/frontend/src/components/MkUpdated.vue index 91f5b86c2d..7cafb1b0af 100644 --- a/packages/frontend/src/components/MkUpdated.vue +++ b/packages/frontend/src/components/MkUpdated.vue @@ -46,8 +46,8 @@ onMounted(() => { max-width: 480px; box-sizing: border-box; text-align: center; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); } .title { diff --git a/packages/frontend/src/components/MkUrlPreview.vue b/packages/frontend/src/components/MkUrlPreview.vue index 04f5314463..0808a052cd 100644 --- a/packages/frontend/src/components/MkUrlPreview.vue +++ b/packages/frontend/src/components/MkUrlPreview.vue @@ -84,13 +84,13 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, onDeactivated, onUnmounted, ref } from 'vue'; -import type { summaly } from '@misskey-dev/summaly'; import { url as local } from '@@/js/config.js'; +import { versatileLang } from '@@/js/intl-const.js'; +import type { summaly } from '@misskey-dev/summaly'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { deviceKind } from '@/scripts/device-kind.js'; import MkButton from '@/components/MkButton.vue'; -import { versatileLang } from '@@/js/intl-const.js'; import { transformPlayerUrl } from '@/scripts/player-url-transform.js'; import { defaultStore } from '@/store.js'; @@ -180,7 +180,7 @@ window.fetch(`/url?url=${encodeURIComponent(requestUrl.href)}&lang=${versatileLa sensitive.value = info.sensitive ?? false; }); -function adjustTweetHeight(message: any) { +function adjustTweetHeight(message: MessageEvent) { if (message.origin !== 'https://platform.twitter.com') return; const embed = message.data?.['twttr.embed']; if (embed?.method !== 'twttr.private.resize') return; @@ -193,14 +193,16 @@ function openPlayer(): void { const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkYouTubePlayer.vue')), { url: requestUrl.href, }, { - // TODO + closed: () => { + dispose(); + }, }); } -(window as any).addEventListener('message', adjustTweetHeight); +window.addEventListener('message', adjustTweetHeight); onUnmounted(() => { - (window as any).removeEventListener('message', adjustTweetHeight); + window.removeEventListener('message', adjustTweetHeight); }); </script> @@ -219,7 +221,7 @@ onUnmounted(() => { height: 1.5em; padding: 0; margin: 0; - color: var(--fg); + color: var(--MI_THEME-fg); background: rgba(128, 128, 128, 0.2); opacity: 0.7; @@ -240,8 +242,8 @@ onUnmounted(() => { position: relative; display: block; font-size: 14px; - box-shadow: 0 0 0 1px var(--divider); - border-radius: var(--radius-sm); + box-shadow: 0 0 0 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); overflow: clip; &:hover { @@ -270,7 +272,7 @@ onUnmounted(() => { height: 100%; background-position: center; background-size: cover; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); display: flex; justify-content: center; align-items: center; @@ -317,7 +319,6 @@ onUnmounted(() => { .siteName { display: inline-block; margin: 0; - color: var(--urlPreviewInfo); font-size: 0.8em; line-height: 16px; vertical-align: top; diff --git a/packages/frontend/src/components/MkUrlWarningDialog.vue b/packages/frontend/src/components/MkUrlWarningDialog.vue index f2e9b11fd9..3bec6eecdd 100644 --- a/packages/frontend/src/components/MkUrlWarningDialog.vue +++ b/packages/frontend/src/components/MkUrlWarningDialog.vue @@ -94,7 +94,7 @@ onBeforeUnmount(() => { min-width: 320px; max-width: 480px; box-sizing: border-box; - background: var(--panel); + background: var(--MI_THEME-panel); border-radius: 16px; } @@ -106,7 +106,7 @@ onBeforeUnmount(() => { .icon { font-size: 18px; - color: var(--warn); + color: var(--MI_THEME-warn); } .title { @@ -117,7 +117,7 @@ onBeforeUnmount(() => { .urlAddress { padding: 10px 14px; border-radius: 8px; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); overflow-x: auto; white-space: nowrap; } diff --git a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue index 3c5f563aa0..fe499fabbf 100644 --- a/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue +++ b/packages/frontend/src/components/MkUserAnnouncementEditDialog.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="dialog" :width="400" @close="dialog?.close()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template v-if="announcement" #header>:{{ announcement.title }}:</template> <template v-else #header>New announcement</template> @@ -25,9 +25,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkRadios v-model="icon"> <template #label>{{ i18n.ts.icon }}</template> <option value="info"><i class="ti ti-info-circle"></i></option> - <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option> - <option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option> - <option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option> + <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i></option> + <option value="error"><i class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i></option> + <option value="success"><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i></option> </MkRadios> <MkRadios v-model="display"> <template #label>{{ i18n.ts.display }}</template> @@ -62,9 +62,16 @@ import MkTextarea from '@/components/MkTextarea.vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkRadios from '@/components/MkRadios.vue'; +type AdminAnnouncementType = Misskey.entities.AdminAnnouncementsCreateRequest & { id: string; } + const props = defineProps<{ user: Misskey.entities.User, - announcement?: Misskey.entities.Announcement, + announcement?: Required<AdminAnnouncementType>, +}>(); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean; updated?: AdminAnnouncementType; created?: AdminAnnouncementType; }): void, + (ev: 'closed'): void }>(); const dialog = ref<InstanceType<typeof MkModalWindow> | null>(null); @@ -74,11 +81,6 @@ const icon = ref(props.announcement ? props.announcement.icon : 'info'); const display = ref(props.announcement ? props.announcement.display : 'dialog'); const needConfirmationToRead = ref(props.announcement ? props.announcement.needConfirmationToRead : false); -const emit = defineEmits<{ - (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, - (ev: 'closed'): void -}>(); - async function done() { const params = { title: title.value, @@ -88,7 +90,7 @@ async function done() { display: display.value, needConfirmationToRead: needConfirmationToRead.value, userId: props.user.id, - }; + } satisfies Misskey.entities.AdminAnnouncementsCreateRequest; if (props.announcement) { await os.apiWithDialog('admin/announcements/update', { @@ -141,8 +143,8 @@ async function del() { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/components/MkUserCardMini.vue b/packages/frontend/src/components/MkUserCardMini.vue index 603f9f2435..ce28f6ec5e 100644 --- a/packages/frontend/src/components/MkUserCardMini.vue +++ b/packages/frontend/src/components/MkUserCardMini.vue @@ -23,7 +23,7 @@ import { acct } from '@/filters/user.js'; const props = withDefaults(defineProps<{ user: Misskey.entities.User; - withChart: boolean; + withChart?: boolean; }>(), { withChart: true, }); @@ -49,8 +49,8 @@ $bodyInfoHieght: 16px; display: flex; align-items: center; padding: 16px; - background: var(--panel); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); } .avatar { @@ -64,7 +64,7 @@ $bodyInfoHieght: 16px; flex: 1; overflow: hidden; font-size: 0.9em; - color: var(--fg); + color: var(--MI_THEME-fg); padding-right: 8px; } diff --git a/packages/frontend/src/components/MkUserInfo.vue b/packages/frontend/src/components/MkUserInfo.vue index 73cdd9ce00..a6bbacacee 100644 --- a/packages/frontend/src/components/MkUserInfo.vue +++ b/packages/frontend/src/components/MkUserInfo.vue @@ -75,9 +75,9 @@ defineProps<{ top: 62px; left: 13px; z-index: 2; - width: var(--avatar); - height: var(--avatar); - border: solid 4px var(--panel); + width: var(--MI-avatar); + height: var(--MI-avatar); + border: solid 4px var(--MI_THEME-panel); } .title { @@ -98,7 +98,7 @@ defineProps<{ margin: 0; line-height: 16px; font-size: 0.8em; - color: var(--fg); + color: var(--MI_THEME-fg); opacity: 0.7; } @@ -110,13 +110,13 @@ defineProps<{ color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .description { padding: 16px; font-size: 0.8em; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .mfm { @@ -128,7 +128,7 @@ defineProps<{ .status { padding: 10px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .statusItem { @@ -139,12 +139,12 @@ defineProps<{ .statusItemLabel { margin: 0; font-size: 0.7em; - color: var(--fg); + color: var(--MI_THEME-fg); } .statusItemValue { font-size: 1em; - color: var(--accent); + color: var(--MI_THEME-accent); } .follow { @@ -169,7 +169,7 @@ defineProps<{ color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); list-style-type: none; margin-left: 0; } diff --git a/packages/frontend/src/components/MkUserList.vue b/packages/frontend/src/components/MkUserList.vue index ac82ecc3d6..8dc01a08ab 100644 --- a/packages/frontend/src/components/MkUserList.vue +++ b/packages/frontend/src/components/MkUserList.vue @@ -39,6 +39,6 @@ const props = withDefaults(defineProps<{ .root { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); + grid-gap: var(--MI-margin); } </style> diff --git a/packages/frontend/src/components/MkUserOnlineIndicator.vue b/packages/frontend/src/components/MkUserOnlineIndicator.vue index 9f04353f62..7a6ded110a 100644 --- a/packages/frontend/src/components/MkUserOnlineIndicator.vue +++ b/packages/frontend/src/components/MkUserOnlineIndicator.vue @@ -36,11 +36,11 @@ const text = computed(() => { <style lang="scss" module> .root { - box-shadow: 0 0 0 3px var(--panel); + box-shadow: 0 0 0 3px var(--MI_THEME-panel); // sharkey: the comment mentions something about 100% radius not behaving correctly on blink. // couldn't reproduce, assuming the 120% here was just an old workaround - border-radius: var(--radius-full); // Blinkã®ãƒã‚°ã‹çŸ¥ã‚‰ã‚“ã‘ã©ã€100%ã´ã£ãŸã‚Šã«ã™ã‚‹ã¨ä½•æ•…ã‹è‹¥å¹²æ¥•円ã§ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã•れる + border-radius: var(--MI-radius-full); // Blinkã®ãƒã‚°ã‹çŸ¥ã‚‰ã‚“ã‘ã©ã€100%ã´ã£ãŸã‚Šã«ã™ã‚‹ã¨ä½•æ•…ã‹è‹¥å¹²æ¥•円ã§ãƒ¬ãƒ³ãƒ€ãƒªãƒ³ã‚°ã•れる &.status_online { background: #58d4c9; diff --git a/packages/frontend/src/components/MkUserPopup.vue b/packages/frontend/src/components/MkUserPopup.vue index 40199c12ff..73e38bef09 100644 --- a/packages/frontend/src/components/MkUserPopup.vue +++ b/packages/frontend/src/components/MkUserPopup.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <svg viewBox="0 0 128 128" :class="$style.avatarBack"> <g transform="matrix(1.6,0,0,1.6,-38.4,-51.2)"> - <path d="M64,32C81.661,32 96,46.339 96,64C95.891,72.184 104,72 104,72C104,72 74.096,80 64,80C52.755,80 24,72 24,72C24,72 31.854,72.018 32,64C32,46.339 46.339,32 64,32Z" style="fill: var(--popup);"/> + <path d="M64,32C81.661,32 96,46.339 96,64C95.891,72.184 104,72 104,72C104,72 74.096,80 64,80C52.755,80 24,72 24,72C24,72 31.854,72.018 32,64C32,46.339 46.339,32 64,32Z" style="fill: var(--MI_THEME-popup);"/> </g> </svg> <MkAvatar :class="$style.avatar" :user="user" indicator/> @@ -162,7 +162,7 @@ onMounted(() => { color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .locked:first-child { @@ -173,7 +173,7 @@ onMounted(() => { color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); } .locked:not(:first-child) { @@ -184,7 +184,7 @@ onMounted(() => { color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); } .avatarBack { @@ -204,8 +204,8 @@ onMounted(() => { right: 0; margin: 0 auto; z-index: 2; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); } .title { @@ -233,15 +233,15 @@ onMounted(() => { padding: 16px 26px; font-size: 0.8em; text-align: center; - border-top: solid 1px var(--divider); - border-bottom: solid 1px var(--divider); + border-top: solid 1px var(--MI_THEME-divider); + border-bottom: solid 1px var(--MI_THEME-divider); } .fields { font-size: 0.8em; padding: 16px; - border-top: solid 1px var(--divider); - border-bottom: solid 1px var(--divider); + border-top: solid 1px var(--MI_THEME-divider); + border-bottom: solid 1px var(--MI_THEME-divider); } .field { @@ -298,7 +298,7 @@ onMounted(() => { .statusItemLabel { font-size: 0.7em; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } .menu { @@ -306,8 +306,8 @@ onMounted(() => { top: 8px; right: 44px; padding: 6px; - background: var(--panel); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-ellipse); } .follow { diff --git a/packages/frontend/src/components/MkUserSelectDialog.vue b/packages/frontend/src/components/MkUserSelectDialog.vue index a5b48c8ce2..85d4666172 100644 --- a/packages/frontend/src/components/MkUserSelectDialog.vue +++ b/packages/frontend/src/components/MkUserSelectDialog.vue @@ -11,7 +11,7 @@ SPDX-License-Identifier: AGPL-3.0-only @click="cancel()" @close="cancel()" @ok="ok()" - @closed="$emit('closed')" + @closed="emit('closed')" > <template #header>{{ i18n.ts.selectUser }}</template> <div> @@ -196,11 +196,11 @@ onMounted(() => { font-size: 14px; &:hover { - background: var(--X7); + background: var(--MI_THEME-X7); } &.selected { - background: var(--accent); + background: var(--MI_THEME-accent); color: #fff; } } diff --git a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue index 1524ea0ec9..5153c06139 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Follow.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Follow.vue @@ -62,7 +62,7 @@ const popularUsers: Paging = { .users { display: grid; grid-template-columns: repeat(auto-fill, minmax(230px, 1fr)); - grid-gap: var(--margin); + grid-gap: var(--MI-margin); justify-content: center; } </style> diff --git a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue index 3194641cdb..7cb48f6afb 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.Profile.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.Profile.vue @@ -51,6 +51,11 @@ watch(name, () => { // 空文å—列をnullã«ã—ãŸã„ã®ã§??ã¯ä½¿ã†ãª // eslint-disable-next-line @typescript-eslint/prefer-nullish-coalescing name: name.value || null, + }, undefined, { + '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': { + title: i18n.ts.yourNameContainsProhibitedWords, + text: i18n.ts.yourNameContainsProhibitedWordsDescription, + }, }); }); diff --git a/packages/frontend/src/components/MkUserSetupDialog.User.vue b/packages/frontend/src/components/MkUserSetupDialog.User.vue index c80349d034..4c4f4989c5 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.User.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.User.vue @@ -59,9 +59,9 @@ async function follow() { top: 30px; left: 13px; z-index: 2; - width: var(--avatar); - height: var(--avatar); - border: solid 4px var(--panel); + width: var(--MI-avatar); + height: var(--MI-avatar); + border: solid 4px var(--MI_THEME-panel); } .title { @@ -82,7 +82,7 @@ async function follow() { margin: 0; line-height: 16px; font-size: 0.8em; - color: var(--fg); + color: var(--MI_THEME-fg); opacity: 0.7; } @@ -99,7 +99,7 @@ async function follow() { } .footer { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); padding: 16px; } </style> diff --git a/packages/frontend/src/components/MkUserSetupDialog.vue b/packages/frontend/src/components/MkUserSetupDialog.vue index 1fb1eda039..b7261129ef 100644 --- a/packages/frontend/src/components/MkUserSetupDialog.vue +++ b/packages/frontend/src/components/MkUserSetupDialog.vue @@ -35,7 +35,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> - <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <i class="ti ti-confetti" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.accountCreated }}</div> <div>{{ i18n.ts._initialAccountSetting.letsStartAccountSetup }}</div> <MkButton primary rounded gradate style="margin: 16px auto 0 auto;" data-cy-user-setup-continue @click="page++">{{ i18n.ts._initialAccountSetting.profileSetting }} <i class="ti ti-arrow-right"></i></MkButton> @@ -91,7 +91,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.centerPage"> <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> - <i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <i class="ti ti-bell-ringing-2" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i> <div style="font-size: 120%;">{{ i18n.ts.pushNotification }}</div> <div style="padding: 0 16px;">{{ i18n.tsx._initialAccountSetting.pushNotificationDescription({ name: instance.name ?? host }) }}</div> <MkPushNotificationAllowButton primary showOnlyToRegister style="margin: 0 auto;"/> @@ -108,7 +108,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkAnimBg style="position: absolute; top: 0;" :scale="1.5"/> <MkSpacer :marginMin="20" :marginMax="28"> <div class="_gaps" style="text-align: center;"> - <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--accent);"></i> + <i class="ti ti-check" style="display: block; margin: auto; font-size: 3em; color: var(--MI_THEME-accent);"></i> <div style="font-size: 120%;">{{ i18n.ts._initialAccountSetting.initialAccountSettingCompleted }}</div> <div>{{ i18n.tsx._initialAccountSetting.youCanContinueTutorial({ name: instance.name ?? host }) }}</div> <div class="_buttonsCenter" style="margin-top: 16px;"> @@ -223,7 +223,7 @@ async function later(later: boolean) { .progressBarValue { height: 100%; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); transition: all 0.5s cubic-bezier(0,.5,.5,1); } @@ -252,7 +252,7 @@ async function later(later: boolean) { left: 0; flex-shrink: 0; padding: 12px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); -webkit-backdrop-filter: blur(15px); backdrop-filter: blur(15px); } diff --git a/packages/frontend/src/components/MkUsersTooltip.vue b/packages/frontend/src/components/MkUsersTooltip.vue index 054a503257..0cb7f22e93 100644 --- a/packages/frontend/src/components/MkUsersTooltip.vue +++ b/packages/frontend/src/components/MkUsersTooltip.vue @@ -16,12 +16,12 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { } from 'vue'; +import * as Misskey from 'misskey-js'; import MkTooltip from './MkTooltip.vue'; defineProps<{ showing: boolean; - users: any[]; // TODO + users: Misskey.entities.UserLite[]; count: number; targetElement: HTMLElement; }>(); diff --git a/packages/frontend/src/components/MkVisibilityPicker.vue b/packages/frontend/src/components/MkVisibilityPicker.vue index 3c3f9e94b6..5624abbd33 100644 --- a/packages/frontend/src/components/MkVisibilityPicker.vue +++ b/packages/frontend/src/components/MkVisibilityPicker.vue @@ -82,7 +82,7 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void { &.asDrawer { padding: 12px 0 max(env(safe-area-inset-bottom, 0px), 12px) 0; width: 100%; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); border-bottom-right-radius: 0; border-bottom-left-radius: 0; @@ -124,7 +124,7 @@ function choose(visibility: typeof Misskey.noteVisibilities[number]): void { } &.active { - color: var(--accent); + color: var(--MI_THEME-accent); } } diff --git a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue index cab42cd59d..d098dad9a1 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.ActiveUsersChart.vue @@ -62,7 +62,7 @@ async function renderChart() { const vLineColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.2)' : 'rgba(0, 0, 0, 0.2)'; const computedStyle = getComputedStyle(document.documentElement); - const accent = tinycolor(computedStyle.getPropertyValue('--accent')).toHexString(); + const accent = tinycolor(computedStyle.getPropertyValue('--MI_THEME-accent')).toHexString(); const colorRead = accent; const colorWrite = '#2ecc71'; diff --git a/packages/frontend/src/components/MkVisitorDashboard.vue b/packages/frontend/src/components/MkVisitorDashboard.vue index 874eff6c79..54f2ee655c 100644 --- a/packages/frontend/src/components/MkVisitorDashboard.vue +++ b/packages/frontend/src/components/MkVisitorDashboard.vue @@ -110,8 +110,8 @@ function showMenu(ev: MouseEvent) { .panel { position: relative; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); box-shadow: 0 12px 32px rgb(0 0 0 / 25%); } @@ -140,7 +140,7 @@ function showMenu(ev: MouseEvent) { right: 16px; width: 32px; height: 32px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); font-size: 18px; z-index: 50; } @@ -191,14 +191,14 @@ function showMenu(ev: MouseEvent) { } .statsItemLabel { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); font-size: 0.9em; } .statsItemCount { font-weight: bold; font-size: 1.2em; - color: var(--accent); + color: var(--MI_THEME-accent); } .tl { @@ -207,7 +207,7 @@ function showMenu(ev: MouseEvent) { .tlHeader { padding: 12px 16px; - border-bottom: solid 1px var(--divider); + border-bottom: solid 1px var(--MI_THEME-divider); } .tlBody { diff --git a/packages/frontend/src/components/MkWaitingDialog.vue b/packages/frontend/src/components/MkWaitingDialog.vue index 60b75b6d30..34fa6b0723 100644 --- a/packages/frontend/src/components/MkWaitingDialog.vue +++ b/packages/frontend/src/components/MkWaitingDialog.vue @@ -47,8 +47,8 @@ watch(() => props.showing, () => { padding: 32px; box-sizing: border-box; text-align: center; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); width: 250px; &.iconOnly { @@ -65,7 +65,7 @@ watch(() => props.showing, () => { font-size: 32px; &.success { - color: var(--accent); + color: var(--MI_THEME-accent); } &.waiting { diff --git a/packages/frontend/src/components/MkWidgets.vue b/packages/frontend/src/components/MkWidgets.vue index 99840bf8d7..b987283a65 100644 --- a/packages/frontend/src/components/MkWidgets.vue +++ b/packages/frontend/src/components/MkWidgets.vue @@ -7,12 +7,12 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.root"> <template v-if="edit"> <header :class="$style.editHeader"> - <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--margin)" data-cy-widget-select> + <MkSelect v-model="widgetAdderSelected" style="margin-bottom: var(--MI-margin)" data-cy-widget-select> <template #label>{{ i18n.ts.selectWidget }}</template> <option v-for="widget in widgetDefs" :key="widget" :value="widget">{{ i18n.ts._widgets[widget] }}</option> </MkSelect> <MkButton inline primary data-cy-widget-add @click="addWidget"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> - <MkButton inline @click="$emit('exit')">{{ i18n.ts.close }}</MkButton> + <MkButton inline @click="emit('exit')">{{ i18n.ts.close }}</MkButton> </header> <Sortable :modelValue="props.widgets" @@ -123,7 +123,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { .widget { contain: content; - margin: var(--margin) 0; + margin: var(--MI-margin) 0; &:first-of-type { margin-top: 0; @@ -158,7 +158,7 @@ function onContextmenu(widget: Widget, ev: MouseEvent) { height: 32px; color: #fff; background: rgba(#000, 0.7); - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); } &Config { diff --git a/packages/frontend/src/components/MkWindow.vue b/packages/frontend/src/components/MkWindow.vue index 08906a1205..2953f656d4 100644 --- a/packages/frontend/src/components/MkWindow.vue +++ b/packages/frontend/src/components/MkWindow.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only :enterFromClass="defaultStore.state.animation ? $style.transition_window_enterFrom : ''" :leaveToClass="defaultStore.state.animation ? $style.transition_window_leaveTo : ''" appear - @afterLeave="$emit('closed')" + @afterLeave="emit('closed')" > <div v-if="showing" ref="rootEl" :class="[$style.root, { [$style.maximized]: maximized }]"> <div :class="$style.body" class="_shadow" @mousedown="onBodyMousedown" @keydown="onKeydown"> @@ -54,12 +54,19 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { onBeforeUnmount, onMounted, provide, shallowRef, ref } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; import contains from '@/scripts/contains.js'; import * as os from '@/os.js'; -import type { MenuItem } from '@/types/menu.js'; import { i18n } from '@/i18n.js'; import { defaultStore } from '@/store.js'; +type WindowButton = { + title: string; + icon: string; + onClick: () => void; + highlighted?: boolean; +}; + const minHeight = 50; const minWidth = 250; @@ -87,8 +94,8 @@ const props = withDefaults(defineProps<{ mini?: boolean; front?: boolean; contextmenu?: MenuItem[] | null; - buttonsLeft?: any[]; - buttonsRight?: any[]; + buttonsLeft?: WindowButton[]; + buttonsRight?: WindowButton[]; }>(), { initialWidth: 400, initialHeight: null, @@ -484,6 +491,10 @@ defineExpose({ } .root { + // universal.vueã¨ã‹ã§ç›´æŽ¥--MI-stickyBottomãŒå®šç¾©ã•れã¦ã„ãŸã‚Šã™ã‚‹ã®ã§ãƒªã‚»ãƒƒãƒˆ + --MI-stickyTop: 0; + --MI-stickyBottom: 0; + position: fixed; top: 0; left: 0; @@ -502,7 +513,7 @@ defineExpose({ contain: content; width: 100%; height: 100%; - border-radius: var(--radius); + border-radius: var(--MI-radius); } .header { @@ -514,10 +525,10 @@ defineExpose({ flex-shrink: 0; user-select: none; height: var(--height); - background: var(--windowHeader); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - //border-bottom: solid 1px var(--divider); + background: var(--MI_THEME-windowHeader); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + //border-bottom: solid 1px var(--MI_THEME-divider); font-size: 90%; font-weight: bold; @@ -531,11 +542,11 @@ defineExpose({ width: var(--height); &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } &.highlighted { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -560,7 +571,7 @@ defineExpose({ .content { flex: 1; overflow: auto; - background: var(--panel); + background: var(--MI_THEME-panel); container-type: size; } diff --git a/packages/frontend/src/components/SkFlashPlayer.vue b/packages/frontend/src/components/SkFlashPlayer.vue index 739a7bc74c..2b61974ef7 100644 --- a/packages/frontend/src/components/SkFlashPlayer.vue +++ b/packages/frontend/src/components/SkFlashPlayer.vue @@ -244,9 +244,9 @@ onDeactivated(() => { } .hide { - border-radius: var(--radius-sm) !important; + border-radius: var(--MI-radius-sm) !important; background-color: black !important; - color: var(--accentLighten) !important; + color: var(--MI_THEME-accentLighten) !important; font-size: 12px !important; } @@ -260,9 +260,9 @@ onDeactivated(() => { > i { display: block; position: absolute; - border-radius: var(--radius-sm); - background-color: var(--fg); - color: var(--accentLighten); + border-radius: var(--MI-radius-sm); + background-color: var(--MI_THEME-fg); + color: var(--MI_THEME-accentLighten); font-size: 14px; opacity: .5; padding: 3px 6px; @@ -276,9 +276,9 @@ onDeactivated(() => { > .alt { display: block; position: absolute; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); background-color: black; - color: var(--accentLighten); + color: var(--MI_THEME-accentLighten); font-size: 0.8em; font-weight: bold; opacity: .5; @@ -311,10 +311,10 @@ onDeactivated(() => { justify-content: center; align-items: center; background: rgba(64, 64, 64, 0.3); - backdrop-filter: var(--modalBgFilter); + backdrop-filter: var(--MI-modalBgFilter); color: #fff; font-size: 12px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); position: absolute; z-index: 4; @@ -334,7 +334,7 @@ onDeactivated(() => { > .controls { display: flex; width: 100%; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); z-index: 5; > * { @@ -344,12 +344,12 @@ onDeactivated(() => { > button, a { border: none; background-color: transparent; - color: var(--accent); + color: var(--MI_THEME-accent); text-decoration: none; cursor: pointer; &:hover { - background-color: var(--fg); + background-color: var(--MI_THEME-fg); } &:disabled { @@ -385,11 +385,11 @@ onDeactivated(() => { outline: none; &::-webkit-slider-runnable-track { - background: var(--bg); + background: var(--MI_THEME-bg); } &::-ms-fill-lower, &::-ms-fill-upper { - background: var(--bg); + background: var(--MI_THEME-bg); } } @@ -398,8 +398,8 @@ onDeactivated(() => { height: 100%; border-radius: 0; animate: 0.2s; - background: var(--bg); - border: 1px solid var(--fg); + background: var(--MI_THEME-bg); + border: 1px solid var(--MI_THEME-fg); overflow-x: hidden; } @@ -408,9 +408,9 @@ onDeactivated(() => { height: 100%; width: 14px; border-radius: 0; - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); -webkit-appearance: none; - box-shadow: calc(-100vw - 14px) 0 0 100vw var(--accent); + box-shadow: calc(-100vw - 14px) 0 0 100vw var(--MI_THEME-accent); clip-path: polygon(1px 0, 100% 0, 100% 100%, 1px 100%, 1px calc(50% + 10.5px), -100vw calc(50% + 10.5px), -100vw calc(50% - 10.5px), 0 calc(50% - 10.5px)); z-index: 1; } @@ -420,13 +420,13 @@ onDeactivated(() => { height: 100%; border-radius: 0; animate: 0.2s; - background: var(--bg); - border: 1px solid var(--fg); + background: var(--MI_THEME-bg); + border: 1px solid var(--MI_THEME-fg); } &::-moz-range-progress { height: 100%; - background: var(--accent); + background: var(--MI_THEME-accent); } &::-moz-range-thumb { @@ -434,7 +434,7 @@ onDeactivated(() => { height: 100%; border-radius: 0; width: 14px; - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } &::-ms-track { @@ -448,14 +448,14 @@ onDeactivated(() => { } &::-ms-fill-lower { - background: var(--accent); - border: 1px solid var(--fg); + background: var(--MI_THEME-accent); + border: 1px solid var(--MI_THEME-fg); border-radius: 0; } &::-ms-fill-upper { - background: var(--bg); - border: 1px solid var(--fg); + background: var(--MI_THEME-bg); + border: 1px solid var(--MI_THEME-fg); border-radius: 0; } @@ -465,7 +465,7 @@ onDeactivated(() => { height: 100%; width: 14px; border-radius: 0; - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } } } diff --git a/packages/frontend/src/components/SkFollowingRecentNotes.vue b/packages/frontend/src/components/SkFollowingRecentNotes.vue index 6daa8feba5..7e71065082 100644 --- a/packages/frontend/src/components/SkFollowingRecentNotes.vue +++ b/packages/frontend/src/components/SkFollowingRecentNotes.vue @@ -118,15 +118,15 @@ function checkMute(note: Misskey.entities.Note | undefined | null, mutes: Mutes) <style module lang="scss"> .panel { - background: var(--panel); + background: var(--MI_THEME-panel); } @keyframes border { from { - border-left: 0 solid var(--accent); + border-left: 0 solid var(--MI_THEME-accent); } to { - border-left: 6px solid var(--accent); + border-left: 6px solid var(--MI_THEME-accent); } } diff --git a/packages/frontend/src/components/SkInstanceTicker.vue b/packages/frontend/src/components/SkInstanceTicker.vue index eb987d9c77..2bfe5cc157 100644 --- a/packages/frontend/src/components/SkInstanceTicker.vue +++ b/packages/frontend/src/components/SkInstanceTicker.vue @@ -45,7 +45,7 @@ const bg = { display: flex; align-items: center; height: 1.5ex; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); padding: 4px; overflow: clip; color: #fff; diff --git a/packages/frontend/src/components/SkMfmWindow.vue b/packages/frontend/src/components/SkMfmWindow.vue index c599531ec5..58ca3d38ab 100644 --- a/packages/frontend/src/components/SkMfmWindow.vue +++ b/packages/frontend/src/components/SkMfmWindow.vue @@ -491,12 +491,12 @@ const preview_fade = ref(`$[fade ðŸ®] $[fade.out ðŸ®] $[fade.speed=3s ðŸ®] $[ > .title { position: sticky; z-index: 1; - top: var(--stickyTop, 0px); + top: var(--MI-stickyTop, 0px); padding: 16px; font-weight: bold; - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); - background-color: var(--X16); + -webkit-backdrop-filter: var(--MI-blur, blur(10px)); + backdrop-filter: var(--MI-blur, blur(10px)); + background-color: var(--MI_THEME-X16); } > .content { @@ -507,7 +507,7 @@ const preview_fade = ref(`$[fade ðŸ®] $[fade.out ðŸ®] $[fade.speed=3s ðŸ®] $[ } > .preview { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); padding: 16px; } } diff --git a/packages/frontend/src/components/SkNote.vue b/packages/frontend/src/components/SkNote.vue index dc8c32a7d9..528e86646b 100644 --- a/packages/frontend/src/components/SkNote.vue +++ b/packages/frontend/src/components/SkNote.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-show="!isDeleted" ref="rootEl" v-hotkey="keymap" - :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover }]" + :class="[$style.root, { [$style.showActionsOnlyHover]: defaultStore.state.showNoteActionsOnlyHover, [$style.skipRender]: defaultStore.state.skipNoteRender }]" :tabindex="isDeleted ? '-1' : '0'" > <SkNoteSub v-if="appearNote.reply" v-show="!renoteCollapsed && !inReplyToCollapsed" :note="appearNote.reply" :class="$style.replyTo"/> @@ -133,7 +133,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" :class="$style.footerButton" class="_button" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @click.stop @mousedown.prevent="renoted ? undoRenote(appearNote) : boostVisibility()" > @@ -157,8 +157,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ph-heart ph-bold ph-lg"></i> </button> <button ref="reactButton" :class="$style.footerButton" class="_button" @click="toggleReact()" @click.stop> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> - <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i> + <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.footerButtonCount">{{ number(appearNote.reactionCount) }}</p> @@ -202,6 +202,9 @@ import { computed, inject, onMounted, ref, shallowRef, Ref, watch, provide } fro import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; +import { shouldCollapsed } from '@@/js/collapsed.js'; +import { host } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import SkNoteSub from '@/components/SkNoteSub.vue'; import SkNoteHeader from '@/components/SkNoteHeader.vue'; import SkNoteSimple from '@/components/SkNoteSimple.vue'; @@ -215,6 +218,7 @@ import MkUrlPreview from '@/components/MkUrlPreview.vue'; import MkButton from '@/components/MkButton.vue'; import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; +import { notePage } from '@/filters/note.js'; import { userPage } from '@/filters/user.js'; import number from '@/filters/number.js'; import * as os from '@/os.js'; @@ -233,13 +237,10 @@ import { deepClone } from '@/scripts/clone.js'; import { useTooltip } from '@/scripts/use-tooltip.js'; import { claimAchievement } from '@/scripts/achievements.js'; import { getNoteSummary } from '@/scripts/get-note-summary.js'; -import type { MenuItem } from '@/types/menu.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { useRouter } from '@/router/supplier.js'; import { boostMenuItems, type Visibility } from '@/scripts/boost-quote.js'; -import { shouldCollapsed } from '@@/js/collapsed.js'; -import { host } from '@@/js/config.js'; import { isEnabledUrlPreview } from '@/instance.js'; import { type Keymap } from '@/scripts/hotkey.js'; import { focusPrev, focusNext } from '@/scripts/focus.js'; @@ -264,6 +265,7 @@ const emit = defineEmits<{ const router = useRouter(); const inTimeline = inject<boolean>('inTimeline', false); +const tl_withSensitive = inject<Ref<boolean>>('tl_withSensitive', ref(true)); const inChannel = inject('inChannel', null); const currentClip = inject<Ref<Misskey.entities.Clip> | null>('currentClip', null); @@ -343,15 +345,18 @@ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly: false): boolean | 'sensitiveMute'; */ function checkMute(noteToCheck: Misskey.entities.Note, mutedWords: Array<string | string[]> | undefined | null, checkOnly = false): boolean | 'sensitiveMute' { - if (mutedWords == null) return false; - - if (checkWordMute(noteToCheck, $i, mutedWords)) return true; - if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true; - if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true; + if (mutedWords != null) { + if (checkWordMute(noteToCheck, $i, mutedWords)) return true; + if (noteToCheck.reply && checkWordMute(noteToCheck.reply, $i, mutedWords)) return true; + if (noteToCheck.renote && checkWordMute(noteToCheck.renote, $i, mutedWords)) return true; + } if (checkOnly) return false; - if (inTimeline && !defaultStore.state.tl.filter.withSensitive && noteToCheck.files?.some((v) => v.isSensitive)) return 'sensitiveMute'; + if (inTimeline && tl_withSensitive.value === false && noteToCheck.files?.some((v) => v.isSensitive)) { + return 'sensitiveMute'; + } + return false; } @@ -514,7 +519,7 @@ function boostVisibility() { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); renoting = true; @@ -564,7 +569,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (props.mock) { return; @@ -625,7 +630,7 @@ function quote() { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); if (props.mock) { return; } @@ -638,7 +643,7 @@ function reply(): void { } function like(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); if (props.mock) { @@ -660,7 +665,7 @@ function like(): void { } function react(viaKeyboard = false): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -808,15 +813,24 @@ function showRenoteMenu(): void { }; } + const renoteDetailsMenu: MenuItem = { + type: 'link', + text: i18n.ts.renoteDetails, + icon: 'ti ti-info-circle', + to: notePage(note.value), + }; + if (isMyRenote) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([ + renoteDetailsMenu, getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), { type: 'divider' }, getUnrenote(), ], renoteTime.value); } else { os.popupMenu([ + renoteDetailsMenu, getCopyNoteLinkMenu(note.value, i18n.ts.copyLinkRenote), { type: 'divider' }, getAbuseNoteMenu(note.value, i18n.ts.reportAbuseRenote), @@ -901,8 +915,8 @@ function emitUpdReaction(emoji: string, delta: number) { margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: solid 2px var(--focus); - border-radius: var(--radius); + border: solid 2px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -929,9 +943,9 @@ function emitUpdReaction(emoji: string, delta: number) { right: 12px; padding: 0 4px; margin-bottom: 0 !important; - background: var(--popup); - border-radius: var(--radius-sm); - box-shadow: 0px 4px 32px var(--shadow); + background: var(--MI_THEME-popup); + border-radius: var(--MI-radius-sm); + box-shadow: 0px 4px 32px var(--MI_THEME-shadow); } .footerButton { @@ -950,6 +964,11 @@ function emitUpdReaction(emoji: string, delta: number) { } } +.skipRender { + content-visibility: auto; + contain-intrinsic-size: 0 150px; +} + .tip { display: flex; align-items: center; @@ -972,18 +991,18 @@ function emitUpdReaction(emoji: string, delta: number) { position: relative; display: flex; align-items: center; - padding: 24px 32px 0 calc(32px + var(--avatar) + 14px); + padding: 24px 32px 0 calc(32px + var(--MI-avatar) + 14px); line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); &::before { content: ''; position: absolute; top: 0; - left: calc(32px + .5 * var(--avatar)); + left: calc(32px + .5 * var(--MI-avatar)); bottom: -8px; - border-left: var(--thread-width) solid var(--thread); + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); } &:first-child { @@ -1072,9 +1091,9 @@ function emitUpdReaction(emoji: string, delta: number) { .collapsedInReplyToLine { position: absolute; - left: calc(32px + .5 * var(--avatar)); + left: calc(32px + .5 * var(--MI-avatar)); // using solid instead of dotted, stylelistic choice - border-left: var(--thread-width) solid var(--thread); + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); top: calc(28px + 28px); // 28px of .root padding, plus 28px of avatar height (see SkNote) height: 28px; } @@ -1090,7 +1109,7 @@ function emitUpdReaction(emoji: string, delta: number) { left: 8px; width: 5px; height: calc(100% - 16px); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); pointer-events: none; } @@ -1099,10 +1118,10 @@ function emitUpdReaction(emoji: string, delta: number) { display: block !important; position: sticky !important; margin: 0 14px 0 0; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); position: sticky !important; - top: calc(22px + var(--stickyTop, 0px)); + top: calc(22px + var(--MI-stickyTop, 0px)); left: 0; transition: top 0.5s; @@ -1128,15 +1147,15 @@ function emitUpdReaction(emoji: string, delta: number) { width: 100%; margin-top: 14px; position: sticky; - bottom: calc(var(--stickyBottom, 0px) - 100px); + bottom: calc(var(--MI-stickyBottom, 0px) - 100px); } .showLessLabel { display: inline-block; - background: var(--popup); + background: var(--MI_THEME-popup); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } @@ -1154,19 +1173,19 @@ function emitUpdReaction(emoji: string, delta: number) { z-index: 2; width: 100%; height: 64px; - //background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + //background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); &:hover > .collapsedLabel { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); } } .collapsedLabel { display: inline-block; - background: var(--panel); + background: var(--MI_THEME-panel); padding: 6px 10px; font-size: 0.8em; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); box-shadow: 0 2px 6px rgb(0 0 0 / 20%); } @@ -1175,13 +1194,13 @@ function emitUpdReaction(emoji: string, delta: number) { } .replyIcon { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -1205,8 +1224,8 @@ function emitUpdReaction(emoji: string, delta: number) { .quoteNote { padding: 16px; // Made border solid, stylistic choice - border: solid 1px var(--renote); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-renote); + border-radius: var(--MI-radius-sm); overflow: clip; } @@ -1229,7 +1248,7 @@ function emitUpdReaction(emoji: string, delta: number) { } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -1242,14 +1261,14 @@ function emitUpdReaction(emoji: string, delta: number) { @container (max-width: 580px) { .root { font-size: 0.95em; - --avatar: 46px; + --MI-avatar: 46px; } .renote { - padding: 24px 26px 0 calc(26px + var(--avatar) + 14px); + padding: 24px 26px 0 calc(26px + var(--MI-avatar) + 14px); &::before { - left: calc(26px + .5 * var(--avatar)); + left: calc(26px + .5 * var(--MI-avatar)); } } @@ -1262,7 +1281,7 @@ function emitUpdReaction(emoji: string, delta: number) { } .collapsedInReplyToLine { - left: calc(26px + .5 * var(--avatar)); + left: calc(26px + .5 * var(--MI-avatar)); } .article { @@ -1284,26 +1303,26 @@ function emitUpdReaction(emoji: string, delta: number) { } .collapsedInReplyToLine { - left: calc(25px + .5 * var(--avatar)); + left: calc(25px + .5 * var(--MI-avatar)); } } @container (max-width: 500px) { .renote { - padding: 23px 25px 0 calc(25px + var(--avatar) + 14px); + padding: 23px 25px 0 calc(25px + var(--MI-avatar) + 14px); &::before { - left: calc(25px + .5 * var(--avatar)); + left: calc(25px + .5 * var(--MI-avatar)); } } } @container (max-width: 480px) { .renote { - padding: 22px 24px 0 calc(24px + var(--avatar) + 14px); + padding: 22px 24px 0 calc(24px + var(--MI-avatar) + 14px); &::before { - left: calc(24px + .5 * var(--avatar)); + left: calc(24px + .5 * var(--MI-avatar)); } } @@ -1321,7 +1340,7 @@ function emitUpdReaction(emoji: string, delta: number) { } .collapsedInReplyToLine { - left: calc(24px + .5 * var(--avatar)); + left: calc(24px + .5 * var(--MI-avatar)); top: calc(22px + 28px); // 22px of .root padding, plus 28px of avatar height } @@ -1332,12 +1351,12 @@ function emitUpdReaction(emoji: string, delta: number) { @container (max-width: 450px) { .root { - --avatar: 44px; + --MI-avatar: 44px; } .avatar { margin: 0 10px 0 0; - top: calc(14px + var(--stickyTop, 0px)); + top: calc(14px + var(--MI-stickyTop, 0px)); } } diff --git a/packages/frontend/src/components/SkNoteDetailed.vue b/packages/frontend/src/components/SkNoteDetailed.vue index 7907da6c94..6c14c6627c 100644 --- a/packages/frontend/src/components/SkNoteDetailed.vue +++ b/packages/frontend/src/components/SkNoteDetailed.vue @@ -58,7 +58,14 @@ SPDX-License-Identifier: AGPL-3.0-only <img v-for="role in appearNote.user.badgeRoles" :key="role.id" v-tooltip="role.name" :class="$style.badgeRole" :src="role.iconUrl"/> </span> </div> - <div :class="$style.noteHeaderUsername"><MkAcct :user="appearNote.user"/></div> + <div :class="$style.noteHeaderUsernameAndBadgeRoles"> + <div :class="$style.noteHeaderUsername"> + <MkAcct :user="appearNote.user"/> + </div> + <div v-if="appearNote.user.badgeRoles" :class="$style.noteHeaderBadgeRoles"> + <img v-for="(role, i) in appearNote.user.badgeRoles" :key="i" v-tooltip="role.name" :class="$style.noteHeaderBadgeRole" :src="role.iconUrl!"/> + </div> + </div> </div> </div> <div style="display: flex; align-items: flex-end; margin-left: auto;"> @@ -143,7 +150,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" class="_button" :class="$style.noteFooterButton" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @mousedown.prevent="renoted ? undoRenote() : boostVisibility()" > <i class="ti ti-repeat"></i> @@ -165,8 +172,8 @@ SPDX-License-Identifier: AGPL-3.0-only <i class="ph-heart ph-bold ph-lg"></i> </button> <button ref="reactButton" :class="$style.noteFooterButton" class="_button" @click="toggleReact()"> - <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--love);"></i> - <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--accent);"></i> + <i v-if="appearNote.reactionAcceptance === 'likeOnly' && appearNote.myReaction != null" class="ti ti-heart-filled" style="color: var(--MI_THEME-love);"></i> + <i v-else-if="appearNote.myReaction != null" class="ti ti-minus" style="color: var(--MI_THEME-accent);"></i> <i v-else-if="appearNote.reactionAcceptance === 'likeOnly'" class="ti ti-heart"></i> <i v-else class="ph-smiley ph-bold ph-lg"></i> <p v-if="(appearNote.reactionAcceptance === 'likeOnly' || defaultStore.state.showReactionsCount) && appearNote.reactionCount > 0" :class="$style.noteFooterButtonCount">{{ number(appearNote.reactionCount) }}</p> @@ -244,6 +251,7 @@ import { computed, inject, onMounted, onUnmounted, onUpdated, provide, ref, shal import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import { isLink } from '@@/js/is-link.js'; +import { host } from '@@/js/config.js'; import SkNoteSub from '@/components/SkNoteSub.vue'; import SkNoteSimple from '@/components/SkNoteSimple.vue'; import MkReactionsViewer from '@/components/MkReactionsViewer.vue'; @@ -267,7 +275,6 @@ import { reactionPicker } from '@/scripts/reaction-picker.js'; import { extractUrlFromMfm } from '@/scripts/extract-url-from-mfm.js'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; -import { host } from '@@/js/config.js'; import { getNoteClipMenu, getNoteMenu } from '@/scripts/get-note-menu.js'; import { getNoteVersionsMenu } from '@/scripts/get-note-versions-menu.js'; import { useNoteCapture } from '@/scripts/use-note-capture.js'; @@ -515,7 +522,7 @@ if (appearNote.value.reactionAcceptance === 'likeOnly') { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); renoting = true; @@ -561,7 +568,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -619,7 +626,7 @@ function quote() { } function reply(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); os.post({ reply: appearNote.value, @@ -630,7 +637,7 @@ function reply(): void { } function react(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.reactionAcceptance === 'likeOnly') { sound.playMisskeySfx('reaction'); @@ -667,7 +674,7 @@ function react(): void { } function like(): void { - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); misskeyApi('notes/like', { @@ -749,7 +756,7 @@ async function clip(): Promise<void> { function showRenoteMenu(): void { if (!isMyRenote) return; - pleaseLogin(undefined, pleaseLoginContext.value); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); os.popupMenu([{ text: i18n.ts.unrenote, icon: 'ti ti-trash', @@ -877,8 +884,8 @@ onUnmounted(() => { margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: dashed 2px var(--focus); - border-radius: var(--radius); + border: dashed 2px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -904,7 +911,7 @@ onUnmounted(() => { padding: 16px 32px 8px 32px; line-height: 28px; white-space: pre; - color: var(--renote); + color: var(--MI_THEME-renote); } .renoteAvatar { @@ -913,7 +920,7 @@ onUnmounted(() => { width: 28px; height: 28px; margin: 0 8px 0 0; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .renoteText { @@ -967,8 +974,8 @@ onUnmounted(() => { margin: auto; width: calc(100% - 8px); height: calc(100% - 8px); - border: solid 1px var(--focus); - border-radius: var(--radius); + border: solid 1px var(--MI_THEME-focus); + border-radius: var(--MI-radius); box-sizing: border-box; } } @@ -985,8 +992,8 @@ onUnmounted(() => { .noteHeaderAvatar { display: block; flex-shrink: 0; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); } .noteHeaderBody { @@ -1009,8 +1016,8 @@ onUnmounted(() => { padding: 4px 6px; font-size: 80%; line-height: 1; - border: solid 0.5px var(--divider); - border-radius: var(--radius-xs); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs); } .noteHeaderInfo { @@ -1018,8 +1025,13 @@ onUnmounted(() => { text-align: right; } +.noteHeaderUsernameAndBadgeRoles { + display: flex; +} + .noteHeaderUsername { margin-bottom: 2px; + margin-right: 0.5em; line-height: 1.3; word-wrap: anywhere; text-overflow: ellipsis; @@ -1034,6 +1046,19 @@ onUnmounted(() => { margin-top: 5px; } +.noteHeaderBadgeRoles { + margin: 0 .5em 0 0; +} + +.noteHeaderBadgeRole { + height: 1.3em; + vertical-align: -20%; + + & + .noteHeaderBadgeRole { + margin-left: 0.2em; + } +} + .noteContent { container-type: inline-size; overflow-wrap: break-word; @@ -1049,19 +1074,19 @@ onUnmounted(() => { } .noteReplyTarget { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .rn { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -1076,8 +1101,8 @@ onUnmounted(() => { .quoteNote { padding: 16px; - border: solid 1px var(--renote); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-renote); + border-radius: var(--MI-radius-sm); overflow: clip; } @@ -1102,7 +1127,7 @@ onUnmounted(() => { } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } @@ -1112,17 +1137,17 @@ onUnmounted(() => { opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } .reply:not(:first-child) { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .tabs { - border-top: solid 0.5px var(--divider); - border-bottom: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); display: flex; } @@ -1141,7 +1166,7 @@ onUnmounted(() => { } .tabActive { - border-bottom: solid 2px var(--accent); + border-bottom: solid 2px var(--MI_THEME-accent); } .tab_renotes { @@ -1161,12 +1186,12 @@ onUnmounted(() => { .reactionTab { padding: 4px 6px; - border: solid 1px var(--divider); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); } .reactionTabActive { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } @container (max-width: 500px) { @@ -1221,7 +1246,7 @@ onUnmounted(() => { margin: 0 10px 0 0 !important; width: 40px !important; height: 40px !important; - border-radius: var(--radius-sm) !important; + border-radius: var(--MI-radius-sm) !important; } .muted { diff --git a/packages/frontend/src/components/SkNoteHeader.vue b/packages/frontend/src/components/SkNoteHeader.vue index 45218fafb6..6bcc30f6cb 100644 --- a/packages/frontend/src/components/SkNoteHeader.vue +++ b/packages/frontend/src/components/SkNoteHeader.vue @@ -161,7 +161,7 @@ const mock = inject<boolean>('mock', false); } &:hover { - color: var(--nameHover); + color: var(--MI_THEME-nameHover); text-decoration: none; } } @@ -188,8 +188,8 @@ const mock = inject<boolean>('mock', false); margin: 0 .5em 0 0; padding: 1px 6px; font-size: 80%; - border: solid 0.5px var(--divider); - border-radius: var(--radius-xs); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs); } .username { @@ -255,7 +255,7 @@ const mock = inject<boolean>('mock', false); } .danger { - color: var(--accent); + color: var(--MI_THEME-accent); } @container (max-width: 500px) { diff --git a/packages/frontend/src/components/SkNoteSimple.vue b/packages/frontend/src/components/SkNoteSimple.vue index b31e337a99..b9895305f2 100644 --- a/packages/frontend/src/components/SkNoteSimple.vue +++ b/packages/frontend/src/components/SkNoteSimple.vue @@ -50,7 +50,7 @@ watch(() => props.expandAllCws, (expandAllCws) => { font-size: 0.95em; &:hover, &:focus-within { - background: var(--panelHighlight); + background: var(--MI_THEME-panelHighlight); transition: background .2s; } } @@ -61,9 +61,9 @@ watch(() => props.expandAllCws, (expandAllCws) => { margin: 0 10px 0 0; width: 34px; height: 34px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); position: sticky !important; - top: calc(16px + var(--stickyTop, 0px)); + top: calc(16px + var(--MI-stickyTop, 0px)); left: 0; } diff --git a/packages/frontend/src/components/SkNoteSub.vue b/packages/frontend/src/components/SkNoteSub.vue index fac35191b9..bd25e1e3ad 100644 --- a/packages/frontend/src/components/SkNoteSub.vue +++ b/packages/frontend/src/components/SkNoteSub.vue @@ -38,7 +38,7 @@ SPDX-License-Identifier: AGPL-3.0-only ref="renoteButton" class="_button" :class="$style.noteFooterButton" - :style="renoted ? 'color: var(--accent) !important;' : ''" + :style="renoted ? 'color: var(--MI_THEME-accent) !important;' : ''" @mousedown="renoted ? undoRenote() : boostVisibility()" > <i class="ph-rocket-launch ph-bold ph-lg"></i> @@ -106,7 +106,8 @@ import { $i } from '@/account.js'; import { userPage } from '@/filters/user.js'; import { checkWordMute } from '@/scripts/check-word-mute.js'; import { defaultStore } from '@/store.js'; -import { pleaseLogin } from '@/scripts/please-login.js'; +import { host } from '@@/js/config.js'; +import { pleaseLogin, type OpenOnRemoteOptions } from '@/scripts/please-login.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import MkRippleEffect from '@/components/MkRippleEffect.vue'; import { reactionPicker } from '@/scripts/reaction-picker.js'; @@ -159,6 +160,11 @@ const isRenote = ( props.note.poll == null ); +const pleaseLoginContext = computed<OpenOnRemoteOptions>(() => ({ + type: 'lookup', + url: `https://${host}/notes/${appearNote.value.id}`, +})); + async function addReplyTo(replyNote: Misskey.entities.Note) { replies.value.unshift(replyNote); appearNote.value.repliesCount += 1; @@ -196,7 +202,7 @@ function focus() { } function reply(viaKeyboard = false): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); os.post({ reply: props.note, @@ -208,7 +214,7 @@ function reply(viaKeyboard = false): void { } function react(viaKeyboard = false): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); if (props.note.reactionAcceptance === 'likeOnly') { @@ -242,7 +248,7 @@ function react(viaKeyboard = false): void { } function like(): void { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); sound.playMisskeySfx('reaction'); misskeyApi('notes/like', { @@ -302,7 +308,7 @@ function boostVisibility() { } function renote(visibility: Visibility, localOnly: boolean = false) { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -346,7 +352,7 @@ function renote(visibility: Visibility, localOnly: boolean = false) { } function quote() { - pleaseLogin(); + pleaseLogin({ openOnRemote: pleaseLoginContext.value }); showMovedDialog(); if (appearNote.value.channel) { @@ -426,7 +432,7 @@ if (props.detail) { padding: 28px 32px; position: relative; - --reply-indent: calc(.5 * var(--avatar)); + --reply-indent: calc(.5 * var(--MI-avatar)); &.children { padding: 10px 0 0 8px; @@ -434,16 +440,16 @@ if (props.detail) { &.isReply { /* @link https://utopia.fyi/clamp/calculator?a=450,580,26—36 */ - --avatar: clamp(26px, -8.6154px + 7.6923cqi, 36px); + --MI-avatar: clamp(26px, -8.6154px + 7.6923cqi, 36px); } } .line { position: absolute; - left: calc(32px + .5 * var(--avatar)); + left: calc(32px + .5 * var(--MI-avatar)); // using solid instead of dotted, stylelistic choice - border-left: var(--thread-width) solid var(--thread); - top: calc(28px + var(--avatar)); // 28px of .root padding, plus 58px of avatar height (see SkNote) + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); + top: calc(28px + var(--MI-avatar)); // 28px of .root padding, plus 58px of avatar height (see SkNote) bottom: -28px; } @@ -468,8 +474,8 @@ if (props.detail) { right: -12px; left: -12px; bottom: -12px; - background: var(--panelHighlight); - border-radius: var(--radius); + background: var(--MI_THEME-panelHighlight); + border-radius: var(--MI-radius); opacity: 0; transition: opacity .2s, background .2s; z-index: -1; @@ -487,7 +493,7 @@ if (props.detail) { left: 8px; width: 5px; height: calc(100% - 8px); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); pointer-events: none; } @@ -495,9 +501,9 @@ if (props.detail) { flex-shrink: 0; display: block; margin: 0 14px 0 0; - width: var(--avatar); - height: var(--avatar); - border-radius: var(--radius-sm); + width: var(--MI-avatar); + height: var(--MI-avatar); + border-radius: var(--MI-radius-sm); } .body { @@ -525,12 +531,12 @@ if (props.detail) { opacity: 0.7; &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } // Responsible for Reply borders 448 and 508 .reply, .more { - //border-left: solid 0.5px var(--divider); + //border-left: solid 0.5px var(--MI_THEME-divider); margin-top: 10px; } @@ -541,11 +547,11 @@ if (props.detail) { @container (max-width: 580px) { .root { padding: 28px 26px 0; - --avatar: 46px; + --MI-avatar: 46px; } .line { - left: calc(26px + .5 * var(--avatar)); + left: calc(26px + .5 * var(--MI-avatar)); } } @@ -555,8 +561,8 @@ if (props.detail) { } .line { - top: calc(23px + var(--avatar)); - left: calc(25px + .5 * var(--avatar)); + top: calc(23px + var(--MI-avatar)); + left: calc(25px + .5 * var(--MI-avatar)); } } @@ -574,7 +580,7 @@ if (props.detail) { opacity: 0.7; &.reacted { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -591,7 +597,7 @@ if (props.detail) { } .reply, .more { - //border-left: solid 0.5px var(--divider); + //border-left: solid 0.5px var(--MI_THEME-divider); margin-top: 10px; } @@ -605,23 +611,23 @@ if (props.detail) { } .line { - top: calc(22px + var(--avatar)); - left: calc(24px + .5 * var(--avatar)); + top: calc(22px + var(--MI-avatar)); + left: calc(24px + .5 * var(--MI-avatar)); } } @container (max-width: 450px) { .root { - --avatar: 44px; + --MI-avatar: 44px; } } .muted { text-align: center; padding: 8px !important; - border: 1px solid var(--divider); + border: 1px solid var(--MI_THEME-divider); margin: 8px 8px 0 8px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } // avatar container with line @@ -633,7 +639,7 @@ if (props.detail) { .threadLine { width: 0; flex-grow: 1; - border-left: var(--thread-width) solid var(--thread); + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); margin-left: var(--reply-indent); } @@ -642,10 +648,10 @@ if (props.detail) { } .reply:not(:last-child) { - border-left: var(--thread-width) solid var(--thread); + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); &::before { - left: calc(-1 * var(--thread-width)); + left: calc(-1 * var(--MI-thread-width)); } } @@ -654,10 +660,10 @@ if (props.detail) { content: ''; left: 0px; top: -10px; - height: calc(10px + 10px + .5 * var(--avatar)); + height: calc(10px + 10px + .5 * var(--MI-avatar)); width: 15px; - border-left: var(--thread-width) solid var(--thread); - border-bottom: var(--thread-width) solid var(--thread); + border-left: var(--MI-thread-width) solid var(--MI_THEME-thread); + border-bottom: var(--MI-thread-width) solid var(--MI_THEME-thread); border-bottom-left-radius: 15px; } diff --git a/packages/frontend/src/components/SkOldNoteWindow.vue b/packages/frontend/src/components/SkOldNoteWindow.vue index 3810b62366..48b9020402 100644 --- a/packages/frontend/src/components/SkOldNoteWindow.vue +++ b/packages/frontend/src/components/SkOldNoteWindow.vue @@ -182,8 +182,8 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS .noteHeaderAvatar { display: block; flex-shrink: 0; - width: var(--avatar); - height: var(--avatar); + width: var(--MI-avatar); + height: var(--MI-avatar); } .noteHeaderBody { @@ -206,8 +206,8 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS padding: 4px 6px; font-size: 80%; line-height: 1; - border: solid 0.5px var(--divider); - border-radius: var(--radius-xs); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius-xs); } .noteHeaderInfo { @@ -240,19 +240,19 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS } .noteReplyTarget { - color: var(--accent); + color: var(--MI_THEME-accent); margin-right: 0.5em; } .rn { margin-left: 4px; font-style: oblique; - color: var(--renote); + color: var(--MI_THEME-renote); } .translation { - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; margin-top: 8px; } @@ -267,8 +267,8 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS .quoteNote { padding: 16px; - border: dashed 1px var(--renote); - border-radius: var(--radius-xs); + border: dashed 1px var(--MI_THEME-renote); + border-radius: var(--MI-radius-xs); overflow: clip; } @@ -287,7 +287,7 @@ const showTicker = (defaultStore.state.instanceTicker === 'always') || (defaultS } &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } diff --git a/packages/frontend/src/components/SkOneko.vue b/packages/frontend/src/components/SkOneko.vue index 24bb392335..ef7bdd74f0 100644 --- a/packages/frontend/src/components/SkOneko.vue +++ b/packages/frontend/src/components/SkOneko.vue @@ -240,6 +240,6 @@ onMounted(init); pointer-events: none; image-rendering: pixelated; z-index: 2147483647; - background-image: var(--oneko-image, url(/client-assets/oneko.gif)); + background-image: var(--MI_THEME-oneko-image, url(/client-assets/oneko.gif)); } </style> diff --git a/packages/frontend/src/components/SkUserRecentNotes.vue b/packages/frontend/src/components/SkUserRecentNotes.vue index 7cfa0fb3f6..908affcdaf 100644 --- a/packages/frontend/src/components/SkUserRecentNotes.vue +++ b/packages/frontend/src/components/SkUserRecentNotes.vue @@ -94,7 +94,7 @@ onMounted(async () => { <style lang="scss" module> .panel { - background: var(--panel); + background: var(--MI_THEME-panel); } .userInfo { diff --git a/packages/frontend/src/components/form/link.vue b/packages/frontend/src/components/form/link.vue index f5546edf1e..b8837d7133 100644 --- a/packages/frontend/src/components/form/link.vue +++ b/packages/frontend/src/components/form/link.vue @@ -60,18 +60,18 @@ const props = defineProps<{ width: 100%; box-sizing: border-box; padding: 10px 14px; - background: var(--folderHeaderBg); - border-radius: var(--radius-sm); + background: var(--MI_THEME-folderHeaderBg); + border-radius: var(--MI-radius-sm); font-size: 0.9em; &:hover { text-decoration: none; - background: var(--folderHeaderHoverBg); + background: var(--MI_THEME-folderHeaderHoverBg); } &.active { - color: var(--accent); - background: var(--folderHeaderHoverBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-folderHeaderHoverBg); } } @@ -79,7 +79,7 @@ const props = defineProps<{ margin-right: 0.75em; flex-shrink: 0; text-align: center; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; diff --git a/packages/frontend/src/components/form/section.vue b/packages/frontend/src/components/form/section.vue index ad37daa265..5fca3acc31 100644 --- a/packages/frontend/src/components/form/section.vue +++ b/packages/frontend/src/components/form/section.vue @@ -21,8 +21,8 @@ defineProps<{ <style lang="scss" module> .root { - border-top: solid 0.5px var(--divider); - //border-bottom: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); + //border-bottom: solid 0.5px var(--MI_THEME-divider); } .rootFirst { @@ -49,7 +49,7 @@ defineProps<{ .description { font-size: 0.85em; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); margin: 0 0 8px 0; } </style> diff --git a/packages/frontend/src/components/form/slot.vue b/packages/frontend/src/components/form/slot.vue index f54db0ca82..da94b7abbb 100644 --- a/packages/frontend/src/components/form/slot.vue +++ b/packages/frontend/src/components/form/slot.vue @@ -35,7 +35,7 @@ function focus() { .caption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); &:empty { display: none; diff --git a/packages/frontend/src/components/form/suspense.vue b/packages/frontend/src/components/form/suspense.vue index 5226c61d68..821f07510b 100644 --- a/packages/frontend/src/components/form/suspense.vue +++ b/packages/frontend/src/components/form/suspense.vue @@ -18,19 +18,19 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> -<script lang="ts" setup> +<script lang="ts" setup generic="T extends unknown"> import { ref, watch } from 'vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; const props = defineProps<{ - p: () => Promise<any>; + p: () => Promise<T>; }>(); const pending = ref(true); const resolved = ref(false); const rejected = ref(false); -const result = ref<any>(null); +const result = ref<T | null>(null); const process = () => { if (props.p == null) { diff --git a/packages/frontend/src/components/global/MkAd.vue b/packages/frontend/src/components/global/MkAd.vue index 1238e6ef23..fc6c64d2aa 100644 --- a/packages/frontend/src/components/global/MkAd.vue +++ b/packages/frontend/src/components/global/MkAd.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<div v-if="chosen && !shouldHide" :class="$style.root"> +<div v-if="chosen && !shouldHide"> <div v-if="!showMenu" :class="[$style.main, { @@ -30,12 +30,10 @@ SPDX-License-Identifier: AGPL-3.0-only </component> </div> <div v-else :class="$style.menu"> - <div :class="$style.menuContainer"> - <div>Ads by {{ host }}</div> - <!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>--> - <MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton> - <button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button> - </div> + <div>Ads by {{ host }}</div> + <!--<MkButton class="button" primary>{{ i18n.ts._ad.like }}</MkButton>--> + <MkButton v-if="chosen.ratio !== 0" :class="$style.menuButton" @click="reduceFrequency">{{ i18n.ts._ad.reduceFrequencyOfThisAd }}</MkButton> + <button class="_textButton" @click="toggleMenu">{{ i18n.ts._ad.back }}</button> </div> </div> <div v-else></div> @@ -43,9 +41,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; +import { url as local, host } from '@@/js/config.js'; import { i18n } from '@/i18n.js'; import { instance } from '@/instance.js'; -import { url as local, host } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import { defaultStore } from '@/store.js'; import * as os from '@/os.js'; @@ -122,11 +120,6 @@ function reduceFrequency(): void { </script> <style lang="scss" module> -.root { - background-size: auto auto; - background-image: repeating-linear-gradient(45deg, transparent, transparent 8px, var(--ad) 8px, var(--ad) 14px ); -} - .main { text-align: center; @@ -139,8 +132,6 @@ function reduceFrequency(): void { } &.form_horizontal { - padding: 8px; - > .link, > .link > .img { max-width: min(600px, 100%); @@ -149,8 +140,6 @@ function reduceFrequency(): void { } &.form_horizontalBig { - padding: 8px; - > .link, > .link > .img { max-width: min(600px, 100%); @@ -182,7 +171,7 @@ function reduceFrequency(): void { display: block; object-fit: contain; margin: auto; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); } .i { @@ -191,8 +180,8 @@ function reduceFrequency(): void { right: 1px; display: grid; place-content: center; - background: var(--panel); - border-radius: var(--radius-full); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-full); padding: 2px; } @@ -202,15 +191,12 @@ function reduceFrequency(): void { } .menu { - padding: 8px; text-align: center; -} - -.menuContainer { padding: 8px; margin: 0 auto; max-width: 400px; - border: solid 1px var(--divider); + background: var(--MI_THEME-panel); + border: solid 1px var(--MI_THEME-divider); } .menuButton { diff --git a/packages/frontend/src/components/global/MkCustomEmoji.vue b/packages/frontend/src/components/global/MkCustomEmoji.vue index fbc716016c..90fa522f3d 100644 --- a/packages/frontend/src/components/global/MkCustomEmoji.vue +++ b/packages/frontend/src/components/global/MkCustomEmoji.vue @@ -25,17 +25,18 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { computed, inject, ref } from 'vue'; +import { computed, defineAsyncComponent, inject, ref } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; import { getProxiedImageUrl, getStaticImageUrl } from '@/scripts/media-proxy.js'; import { defaultStore } from '@/store.js'; import { customEmojisMap } from '@/custom-emojis.js'; import * as os from '@/os.js'; -import { misskeyApiGet } from '@/scripts/misskey-api.js'; +import { misskeyApi, misskeyApiGet } from '@/scripts/misskey-api.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import * as sound from '@/scripts/sound.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; -import type { MenuItem } from '@/types/menu.js'; +import { $i } from '@/account.js'; const props = defineProps<{ name: string; @@ -127,9 +128,31 @@ function onClick(ev: MouseEvent) { }, }); + if ($i?.isModerator ?? $i?.isAdmin) { + menuItems.push({ + text: i18n.ts.edit, + icon: 'ti ti-pencil', + action: async () => { + await edit(props.name); + }, + }); + } + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } } + +async function edit(name: string) { + const emoji = await misskeyApi('emoji', { + name: name, + }); + const { dispose } = os.popup(defineAsyncComponent(() => import('@/pages/emoji-edit-dialog.vue')), { + emoji: emoji, + }, { + closed: () => dispose(), + }); +} + </script> <style lang="scss" module> diff --git a/packages/frontend/src/components/global/MkError.vue b/packages/frontend/src/components/global/MkError.vue index 64c828ba4a..77dddaff89 100644 --- a/packages/frontend/src/components/global/MkError.vue +++ b/packages/frontend/src/components/global/MkError.vue @@ -44,6 +44,6 @@ const emit = defineEmits<{ width: 128px; height: 128px; margin-bottom: 16px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } </style> diff --git a/packages/frontend/src/components/global/MkLoading.vue b/packages/frontend/src/components/global/MkLoading.vue index 49d8ace37b..47d797606b 100644 --- a/packages/frontend/src/components/global/MkLoading.vue +++ b/packages/frontend/src/components/global/MkLoading.vue @@ -56,7 +56,7 @@ const props = withDefaults(defineProps<{ --size: 38px; &.colored { - color: var(--accent); + color: var(--MI_THEME-accent); } &.inline { diff --git a/packages/frontend/src/components/global/MkMfm.ts b/packages/frontend/src/components/global/MkMfm.ts index 9bf9f4a872..1039572a06 100644 --- a/packages/frontend/src/components/global/MkMfm.ts +++ b/packages/frontend/src/components/global/MkMfm.ts @@ -7,6 +7,7 @@ import { VNode, h, defineAsyncComponent, SetupContext, provide } from 'vue'; import * as mfm from '@transfem-org/sfm-js'; import * as Misskey from 'misskey-js'; import CkFollowMouse from '../CkFollowMouse.vue'; +import { host } from '@@/js/config.js'; import MkUrl from '@/components/global/MkUrl.vue'; import MkTime from '@/components/global/MkTime.vue'; import MkLink from '@/components/MkLink.vue'; @@ -18,7 +19,6 @@ import MkCodeInline from '@/components/MkCodeInline.vue'; import MkGoogle from '@/components/MkGoogle.vue'; import MkSparkle from '@/components/MkSparkle.vue'; import MkA, { MkABehavior } from '@/components/global/MkA.vue'; -import { host } from '@@/js/config.js'; import { defaultStore } from '@/store.js'; function safeParseFloat(str: unknown): number | null { @@ -32,8 +32,8 @@ const QUOTE_STYLE = ` display: block; margin: 8px; padding: 6px 0 6px 12px; -color: var(--fg); -border-left: solid 3px var(--fg); +color: var(--MI_THEME-fg); +border-left: solid 3px var(--MI_THEME-fg); opacity: 0.7; `.split('\n').join(' '); @@ -60,7 +60,8 @@ type MfmEvents = { // eslint-disable-next-line import/no-default-export export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEvents>['emit'] }) { - provide('linkNavigationBehavior', props.linkNavigationBehavior); + // ã“ã†ã—ãŸã„ã¨ã“ã‚ã ã‘ã© functional component 内ã§ã¯ provide ã¯ä½¿ãˆãªã„ + //provide('linkNavigationBehavior', props.linkNavigationBehavior); const isNote = props.isNote ?? true; const shouldNyaize = props.nyaize === 'respect' && props.author?.isCat && props.author?.speakAsCat && !defaultStore.state.disableCatSpeak; @@ -328,7 +329,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven } case 'border': { let color = validColor(token.props.args.color); - color = color ? `#${color}` : 'var(--accent)'; + color = color ? `#${color}` : 'var(--MI_THEME-accent)'; let b_style = token.props.args.style; if ( typeof b_style !== 'string' || @@ -361,7 +362,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven const child = token.children[0]; const unixtime = parseInt(child.type === 'text' ? child.props.text : ''); return h('span', { - style: 'display: inline-block; font-size: 90%; border: solid 1px var(--divider); border-radius: var(--radius-ellipse); padding: 4px 10px 4px 6px;', + style: 'display: inline-block; font-size: 90%; border: solid 1px var(--MI_THEME-divider); border-radius: var(--MI-radius-ellipse); padding: 4px 10px 4px 6px;', }, [ h('i', { class: 'ti ti-clock', @@ -409,6 +410,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven key: Math.random(), url: token.props.url, rel: 'nofollow noopener', + navigationBehavior: props.linkNavigationBehavior, }))]; } @@ -417,6 +419,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven key: Math.random(), url: token.props.url, rel: 'nofollow noopener', + navigationBehavior: props.linkNavigationBehavior, }, genEl(token.children, scale, true)))]; } @@ -425,6 +428,7 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven key: Math.random(), host: (token.props.host == null && props.author && props.author.host != null ? props.author.host : token.props.host) ?? host, username: token.props.username, + navigationBehavior: props.linkNavigationBehavior, }))]; } @@ -432,7 +436,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven return [h('bdi', h(MkA, { key: Math.random(), to: isNote ? `/tags/${encodeURIComponent(token.props.hashtag)}` : `/user-tags/${encodeURIComponent(token.props.hashtag)}`, - style: 'color:var(--hashtag);', + style: 'color:var(--MI_THEME-hashtag);', + behavior: props.linkNavigationBehavior, }, `#${token.props.hashtag}`))]; } @@ -527,8 +532,8 @@ export default function (props: MfmProps, { emit }: { emit: SetupContext<MfmEven } default: { - // eslint-disable-next-line @typescript-eslint/no-explicit-any - console.error('unrecognized ast type:', (token as any).type); + // @ts-expect-error å˜åœ¨ã—ãªã„ASTタイプ + console.error('unrecognized ast type:', token.type); return []; } diff --git a/packages/frontend/src/components/global/MkPageHeader.tabs.vue b/packages/frontend/src/components/global/MkPageHeader.tabs.vue index 7d13fb9279..ffa6f13ff6 100644 --- a/packages/frontend/src/components/global/MkPageHeader.tabs.vue +++ b/packages/frontend/src/components/global/MkPageHeader.tabs.vue @@ -53,7 +53,7 @@ export type Tab = { </script> <script lang="ts" setup> -import { onMounted, onUnmounted, watch, nextTick, shallowRef } from 'vue'; +import { nextTick, onMounted, onUnmounted, shallowRef, watch } from 'vue'; import { defaultStore } from '@/store.js'; const props = withDefaults(defineProps<{ @@ -120,14 +120,14 @@ function onTabWheel(ev: WheelEvent) { let entering = false; -async function enter(element: Element) { +async function enter(el: Element) { + if (!(el instanceof HTMLElement)) return; entering = true; - const el = element as HTMLElement; const elementWidth = el.getBoundingClientRect().width; el.style.width = '0'; el.style.paddingLeft = '0'; - el.offsetWidth; // force reflow - el.style.width = elementWidth + 'px'; + el.offsetWidth; // reflow + el.style.width = `${elementWidth}px`; el.style.paddingLeft = ''; nextTick(() => { entering = false; @@ -136,22 +136,23 @@ async function enter(element: Element) { setTimeout(renderTab, 170); } -function afterEnter(element: Element) { - //el.style.width = ''; +function afterEnter(el: Element) { + if (!(el instanceof HTMLElement)) return; + // element.style.width = ''; } -async function leave(element: Element) { - const el = element as HTMLElement; +async function leave(el: Element) { + if (!(el instanceof HTMLElement)) return; const elementWidth = el.getBoundingClientRect().width; - el.style.width = elementWidth + 'px'; + el.style.width = `${elementWidth}px`; el.style.paddingLeft = ''; - el.offsetWidth; // force reflow + el.offsetWidth; // reflow el.style.width = '0'; el.style.paddingLeft = '0'; } -function afterLeave(element: Element) { - const el = element as HTMLElement; +function afterLeave(el: Element) { + if (!(el instanceof HTMLElement)) return; el.style.width = ''; } @@ -219,7 +220,7 @@ onUnmounted(() => { &.active { opacity: 1; - color: var(--accent); + color: var(--MI_THEME-accent); } &.animate { @@ -248,8 +249,8 @@ onUnmounted(() => { position: absolute; bottom: 0; height: 3px; - background: var(--accent); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-accent); + border-radius: var(--MI-radius-ellipse); transition: none; pointer-events: none; diff --git a/packages/frontend/src/components/global/MkPageHeader.vue b/packages/frontend/src/components/global/MkPageHeader.vue index bb20f54fa4..18c97b1bdb 100644 --- a/packages/frontend/src/components/global/MkPageHeader.vue +++ b/packages/frontend/src/components/global/MkPageHeader.vue @@ -115,7 +115,7 @@ function goBack(): void { } const calcBg = () => { - const rawBg = 'var(--bg)'; + const rawBg = 'var(--MI_THEME-bg)'; const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); @@ -146,9 +146,9 @@ onUnmounted(() => { <style lang="scss" module> .root { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-bottom: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + border-bottom: solid 0.5px var(--MI_THEME-divider); width: 100%; } @@ -161,7 +161,7 @@ onUnmounted(() => { .upper { --height: 50px; display: flex; - gap: var(--margin); + gap: var(--MI-margin); height: var(--height); .tabs:first-child { @@ -239,14 +239,14 @@ onUnmounted(() => { width: calc(var(--height) - (var(--margin))); box-sizing: border-box; position: relative; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); &:hover { background: rgba(0, 0, 0, 0.05); } &.highlighted { - color: var(--accent); + color: var(--MI_THEME-accent); } } diff --git a/packages/frontend/src/components/global/MkStickyContainer.vue b/packages/frontend/src/components/global/MkStickyContainer.vue index 72993991ce..1aebf487bb 100644 --- a/packages/frontend/src/components/global/MkStickyContainer.vue +++ b/packages/frontend/src/components/global/MkStickyContainer.vue @@ -5,32 +5,30 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div ref="rootEl"> - <div ref="headerEl"> + <div ref="headerEl" :class="$style.header"> <slot name="header"></slot> </div> <div - ref="bodyEl" + :class="$style.body" :data-sticky-container-header-height="headerHeight" :data-sticky-container-footer-height="footerHeight" - style="position: relative; z-index: 0;" > <slot></slot> </div> - <div ref="footerEl"> + <div ref="footerEl" :class="$style.footer"> <slot name="footer"></slot> </div> </div> </template> <script lang="ts" setup> -import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, shallowRef } from 'vue'; +import { onMounted, onUnmounted, provide, inject, Ref, ref, watch, useTemplateRef } from 'vue'; import { CURRENT_STICKY_BOTTOM, CURRENT_STICKY_TOP } from '@@/js/const.js'; -const rootEl = shallowRef<HTMLElement>(); -const headerEl = shallowRef<HTMLElement>(); -const footerEl = shallowRef<HTMLElement>(); -const bodyEl = shallowRef<HTMLElement>(); +const rootEl = useTemplateRef('rootEl'); +const headerEl = useTemplateRef('headerEl'); +const footerEl = useTemplateRef('footerEl'); const headerHeight = ref<string | undefined>(); const childStickyTop = ref(0); @@ -67,31 +65,11 @@ onMounted(() => { watch([parentStickyTop, parentStickyBottom], calc); - watch(childStickyTop, () => { - if (bodyEl.value == null) return; - bodyEl.value.style.setProperty('--stickyTop', `${childStickyTop.value}px`); - }, { - immediate: true, - }); - - watch(childStickyBottom, () => { - if (bodyEl.value == null) return; - bodyEl.value.style.setProperty('--stickyBottom', `${childStickyBottom.value}px`); - }, { - immediate: true, - }); - if (headerEl.value != null) { - headerEl.value.style.position = 'sticky'; - headerEl.value.style.top = 'var(--stickyTop, 0)'; - headerEl.value.style.zIndex = '1'; observer.observe(headerEl.value); } if (footerEl.value != null) { - footerEl.value.style.position = 'sticky'; - footerEl.value.style.bottom = 'var(--stickyBottom, 0)'; - footerEl.value.style.zIndex = '1'; observer.observe(footerEl.value); } }); @@ -101,6 +79,27 @@ onUnmounted(() => { }); defineExpose({ - rootEl: rootEl, + rootEl, }); </script> + +<style lang='scss' module> +.body { + position: relative; + z-index: 0; + --MI-stickyTop: v-bind("childStickyTop + 'px'"); + --MI-stickyBottom: v-bind("childStickyBottom + 'px'"); +} + +.header { + position: sticky; + top: var(--MI-stickyTop, 0); + z-index: 1; +} + +.footer { + position: sticky; + bottom: var(--MI-stickyBottom, 0); + z-index: 1; +} +</style> diff --git a/packages/frontend/src/components/global/MkTime.vue b/packages/frontend/src/components/global/MkTime.vue index 50bec990a1..f600f7eed2 100644 --- a/packages/frontend/src/components/global/MkTime.vue +++ b/packages/frontend/src/components/global/MkTime.vue @@ -99,10 +99,10 @@ if (!invalid && props.origin === null && (props.mode === 'relative' || props.mod <style lang="scss" module> .old1 { - color: var(--warn); + color: var(--MI_THEME-warn); } .old1.old2 { - color: var(--error); + color: var(--MI_THEME-error); } </style> diff --git a/packages/frontend/src/components/global/RouterView.vue b/packages/frontend/src/components/global/RouterView.vue index 19bd794a5d..38bdfc52d4 100644 --- a/packages/frontend/src/components/global/RouterView.vue +++ b/packages/frontend/src/components/global/RouterView.vue @@ -27,6 +27,7 @@ import MkLoadingPage from '@/pages/_loading_.vue'; const props = defineProps<{ router?: IRouter; + nested?: boolean; }>(); const router = props.router ?? inject('router'); @@ -39,6 +40,8 @@ const currentDepth = inject('routerCurrentDepth', 0); provide('routerCurrentDepth', currentDepth + 1); function resolveNested(current: Resolved, d = 0): Resolved | null { + if (!props.nested) return current; + if (d === currentDepth) { return current; } else { diff --git a/packages/frontend/src/components/page/page.dynamic.vue b/packages/frontend/src/components/page/page.dynamic.vue index 8c511a690d..c2449931c1 100644 --- a/packages/frontend/src/components/page/page.dynamic.vue +++ b/packages/frontend/src/components/page/page.dynamic.vue @@ -27,9 +27,9 @@ const props = defineProps<{ <style lang="scss" module> .root { - border: 1px solid var(--divider); - border-radius: var(--radius); - padding: var(--margin); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); + padding: var(--MI-margin); text-align: center; } diff --git a/packages/frontend/src/components/page/page.image.vue b/packages/frontend/src/components/page/page.image.vue index fc1ce9fc7b..69443ce7dd 100644 --- a/packages/frontend/src/components/page/page.image.vue +++ b/packages/frontend/src/components/page/page.image.vue @@ -28,8 +28,8 @@ onMounted(() => { <style lang="scss" module> .root { - border: 1px solid var(--divider); - border-radius: var(--radius); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); overflow: hidden; } .mediaList { diff --git a/packages/frontend/src/components/page/page.note.vue b/packages/frontend/src/components/page/page.note.vue index b5ba407806..84436e7adb 100644 --- a/packages/frontend/src/components/page/page.note.vue +++ b/packages/frontend/src/components/page/page.note.vue @@ -35,7 +35,7 @@ onMounted(() => { <style lang="scss" module> .root { - border: 1px solid var(--divider); - border-radius: var(--radius); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius); } </style> diff --git a/packages/frontend/src/directives/adaptive-bg.ts b/packages/frontend/src/directives/adaptive-bg.ts index 23fd1bddf4..f88996019f 100644 --- a/packages/frontend/src/directives/adaptive-bg.ts +++ b/packages/frontend/src/directives/adaptive-bg.ts @@ -4,24 +4,16 @@ */ import { Directive } from 'vue'; +import { getBgColor } from '@/scripts/get-bg-color.js'; export default { mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { - const style = window.getComputedStyle(el); - if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { - return style.backgroundColor; - } else { - return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; - } - }; - - const parentBg = getBgColor(src.parentElement); + const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const myBg = window.getComputedStyle(src).backgroundColor; if (parentBg === myBg) { - src.style.backgroundColor = 'var(--bg)'; + src.style.backgroundColor = 'var(--MI_THEME-bg)'; } else { src.style.backgroundColor = myBg; } diff --git a/packages/frontend/src/directives/adaptive-border.ts b/packages/frontend/src/directives/adaptive-border.ts index b436075fcd..1305f312bd 100644 --- a/packages/frontend/src/directives/adaptive-border.ts +++ b/packages/frontend/src/directives/adaptive-border.ts @@ -4,24 +4,16 @@ */ import { Directive } from 'vue'; +import { getBgColor } from '@/scripts/get-bg-color.js'; export default { mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { - const style = window.getComputedStyle(el); - if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { - return style.backgroundColor; - } else { - return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; - } - }; - - const parentBg = getBgColor(src.parentElement); + const parentBg = getBgColor(src.parentElement) ?? 'transparent'; const myBg = window.getComputedStyle(src).backgroundColor; if (parentBg === myBg) { - src.style.borderColor = 'var(--divider)'; + src.style.borderColor = 'var(--MI_THEME-divider)'; } else { src.style.borderColor = myBg; } diff --git a/packages/frontend/src/directives/panel.ts b/packages/frontend/src/directives/panel.ts index bbcc220e09..aa26b94d0b 100644 --- a/packages/frontend/src/directives/panel.ts +++ b/packages/frontend/src/directives/panel.ts @@ -4,26 +4,18 @@ */ import { Directive } from 'vue'; +import { getBgColor } from '@/scripts/get-bg-color.js'; export default { mounted(src, binding, vn) { - const getBgColor = (el: HTMLElement) => { - const style = window.getComputedStyle(el); - if (style.backgroundColor && !['rgba(0, 0, 0, 0)', 'rgba(0,0,0,0)', 'transparent'].includes(style.backgroundColor)) { - return style.backgroundColor; - } else { - return el.parentElement ? getBgColor(el.parentElement) : 'transparent'; - } - }; + const parentBg = getBgColor(src.parentElement) ?? 'transparent'; - const parentBg = getBgColor(src.parentElement); - - const myBg = getComputedStyle(document.documentElement).getPropertyValue('--panel'); + const myBg = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'); if (parentBg === myBg) { - src.style.backgroundColor = 'var(--bg)'; + src.style.backgroundColor = 'var(--MI_THEME-bg)'; } else { - src.style.backgroundColor = 'var(--panel)'; + src.style.backgroundColor = 'var(--MI_THEME-panel)'; } }, } as Directive; diff --git a/packages/frontend/src/nirax.ts b/packages/frontend/src/nirax.ts index 25f853453a..965bd6f0bc 100644 --- a/packages/frontend/src/nirax.ts +++ b/packages/frontend/src/nirax.ts @@ -36,6 +36,8 @@ interface RouteDefWithRedirect extends RouteDefBase { export type RouteDef = RouteDefWithComponent | RouteDefWithRedirect; +export type RouterFlag = 'forcePage'; + type ParsedPath = (string | { name: string; startsWith?: string; @@ -107,7 +109,7 @@ export interface IRouter extends EventEmitter<RouterEvent> { current: Resolved; currentRef: ShallowRef<Resolved>; currentRoute: ShallowRef<RouteDef>; - navHook: ((path: string, flag?: any) => boolean) | null; + navHook: ((path: string, flag?: RouterFlag) => boolean) | null; /** * ルートã®åˆæœŸåŒ–(eventListenerã®å®šç¾©å¾Œã«å¿…ãšå‘¼ã³å‡ºã™ã“ã¨ï¼‰ @@ -116,11 +118,11 @@ export interface IRouter extends EventEmitter<RouterEvent> { resolve(path: string): Resolved | null; - getCurrentPath(): any; + getCurrentPath(): string; getCurrentKey(): string; - push(path: string, flag?: any): void; + push(path: string, flag?: RouterFlag): void; replace(path: string, key?: string | null): void; @@ -197,7 +199,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter { private currentKey = Date.now().toString(); private redirectCount = 0; - public navHook: ((path: string, flag?: any) => boolean) | null = null; + public navHook: ((path: string, flag?: RouterFlag) => boolean) | null = null; constructor(routes: Router['routes'], currentPath: Router['currentPath'], isLoggedIn: boolean, notFoundPageComponent: Component) { super(); @@ -404,7 +406,7 @@ export class Router extends EventEmitter<RouterEvent> implements IRouter { return this.currentKey; } - public push(path: string, flag?: any) { + public push(path: string, flag?: RouterFlag) { const beforePath = this.currentPath; if (path === beforePath) { this.emit('same'); diff --git a/packages/frontend/src/os.ts b/packages/frontend/src/os.ts index deb629d534..a81f67aef3 100644 --- a/packages/frontend/src/os.ts +++ b/packages/frontend/src/os.ts @@ -10,6 +10,7 @@ import { EventEmitter } from 'eventemitter3'; import * as Misskey from 'misskey-js'; import type { ComponentProps as CP } from 'vue-component-type-helpers'; import type { Form, GetFormResultType } from '@/scripts/form.js'; +import type { MenuItem } from '@/types/menu.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; @@ -22,19 +23,20 @@ import MkPasswordDialog from '@/components/MkPasswordDialog.vue'; import MkEmojiPickerDialog from '@/components/MkEmojiPickerDialog.vue'; import MkPopupMenu from '@/components/MkPopupMenu.vue'; import MkContextMenu from '@/components/MkContextMenu.vue'; -import type { MenuItem } from '@/types/menu.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { pleaseLogin } from '@/scripts/please-login.js'; import { showMovedDialog } from '@/scripts/show-moved-dialog.js'; import { getHTMLElementOrNull } from '@/scripts/get-dom-node-or-null.js'; import { focusParent } from '@/scripts/focus.js'; +import type { PostFormProps } from '@/types/post-form.js'; export const openingWindowsCount = ref(0); -export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>( +export const apiWithDialog = (<E extends keyof Misskey.Endpoints, P extends Misskey.Endpoints[E]['req'] = Misskey.Endpoints[E]['req']>( endpoint: E, - data: P = {} as any, + data: P, token?: string | null | undefined, + customErrors?: Record<string, { title?: string; text: string; }>, ) => { const promise = misskeyApi(endpoint, data, token); promiseDialog(promise, null, async (err) => { @@ -77,6 +79,9 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey } else if (err.message.startsWith('Unexpected token')) { title = i18n.ts.gotInvalidResponseError; text = i18n.ts.gotInvalidResponseErrorDescription; + } else if (customErrors && customErrors[err.id] != null) { + title = customErrors[err.id].title; + text = customErrors[err.id].text; } alert({ type: 'error', @@ -86,11 +91,11 @@ export const apiWithDialog = (<E extends keyof Misskey.Endpoints = keyof Misskey }); return promise; -}) as typeof misskeyApi; +}); export function promiseDialog<T extends Promise<any>>( promise: T, - onSuccess?: ((res: any) => void) | null, + onSuccess?: ((res: Awaited<T>) => void) | null, onFailure?: ((err: Misskey.api.APIError) => void) | null, text?: string, ): T { @@ -139,12 +144,12 @@ export function promiseDialog<T extends Promise<any>>( } let popupIdCount = 0; -export const popups = ref([]) as Ref<{ +export const popups = ref<{ id: number; component: Component; props: Record<string, any>; events: Record<string, any>; -}[]>; +}[]>([]); const zIndexes = { veryLow: 500000, @@ -463,7 +468,7 @@ type SelectItem<C> = { }; // default ãŒæŒ‡å®šã•れã¦ã„ãŸã‚‰ result 㯠null ã«ãªã‚Šå¾—ãªã„ã“ã¨ã‚’ä¿è¨¼ã™ã‚‹ overload function -export function select<C = any>(props: { +export function select<C = unknown>(props: { title?: string; text?: string; default: string; @@ -476,7 +481,7 @@ export function select<C = any>(props: { } | { canceled: false; result: C; }>; -export function select<C = any>(props: { +export function select<C = unknown>(props: { title?: string; text?: string; default?: string | null; @@ -489,7 +494,7 @@ export function select<C = any>(props: { } | { canceled: false; result: C | null; }>; -export function select<C = any>(props: { +export function select<C = unknown>(props: { title?: string; text?: string; default?: string | null; @@ -692,15 +697,17 @@ export function contextMenu(items: MenuItem[], ev: MouseEvent): Promise<void> { })); } -export function post(props: Record<string, any> = {}): Promise<void | boolean> { - pleaseLogin(undefined, (props.initialText || props.initialNote ? { - type: 'share', - params: { - text: props.initialText ?? props.initialNote.text, - visibility: props.initialVisibility ?? props.initialNote?.visibility, - localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0', - }, - } : undefined)); +export function post(props: PostFormProps = {}): Promise<void | boolean> { + pleaseLogin({ + openOnRemote: (props.initialText || props.initialNote ? { + type: 'share', + params: { + text: props.initialText ?? props.initialNote?.text ?? '', + visibility: props.initialVisibility ?? props.initialNote?.visibility ?? 'public', + localOnly: (props.initialLocalOnly || props.initialNote?.localOnly) ? '1' : '0', + }, + } : undefined), + }); showMovedDialog(); return new Promise(resolve => { diff --git a/packages/frontend/src/pages/_error_.vue b/packages/frontend/src/pages/_error_.vue index 83f596b870..0bafc385a6 100644 --- a/packages/frontend/src/pages/_error_.vue +++ b/packages/frontend/src/pages/_error_.vue @@ -83,6 +83,6 @@ definePageMetadata(() => ({ vertical-align: bottom; height: 128px; margin-bottom: 24px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } </style> diff --git a/packages/frontend/src/pages/about-sharkey.vue b/packages/frontend/src/pages/about-sharkey.vue index f689f9b98e..ac2bb9b14e 100644 --- a/packages/frontend/src/pages/about-sharkey.vue +++ b/packages/frontend/src/pages/about-sharkey.vue @@ -352,7 +352,7 @@ definePageMetadata(() => ({ .znqjceqz { > .about { position: relative; - border-radius: var(--radius); + border-radius: var(--MI-radius); > .treasure { position: absolute; @@ -391,7 +391,7 @@ definePageMetadata(() => ({ display: block; width: 80px; margin: 0 auto; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); position: relative; z-index: 1; transform: translateX(-10%); @@ -441,23 +441,23 @@ definePageMetadata(() => ({ display: flex; align-items: center; padding: 12px; - background: var(--buttonBg); - border-radius: var(--radius-sm); + background: var(--MI_THEME-buttonBg); + border-radius: var(--MI-radius-sm); &:hover { text-decoration: none; - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &.active { - color: var(--accent); - background: var(--buttonHoverBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-buttonHoverBg); } } .contributorAvatar { width: 30px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .contributorUsername { @@ -474,13 +474,13 @@ definePageMetadata(() => ({ display: flex; align-items: center; padding: 12px; - background: var(--buttonBg); - border-radius: var(--radius-sm); + background: var(--MI_THEME-buttonBg); + border-radius: var(--MI-radius-sm); } .patronIcon { width: 24px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .patronName { diff --git a/packages/frontend/src/pages/about.federation.vue b/packages/frontend/src/pages/about.federation.vue index 71353c7dfa..2190be8bec 100644 --- a/packages/frontend/src/pages/about.federation.vue +++ b/packages/frontend/src/pages/about.federation.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #prefix><i class="ti ti-search"></i></template> <template #label>{{ i18n.ts.host }}</template> </MkInput> - <FormSplit style="margin-top: var(--margin);"> + <FormSplit style="margin-top: var(--MI-margin);"> <MkSelect v-model="state"> <template #label>{{ i18n.ts.state }}</template> <option value="all">{{ i18n.ts.all }}</option> diff --git a/packages/frontend/src/pages/about.overview.vue b/packages/frontend/src/pages/about.overview.vue index ca070c65a4..347214c8fa 100644 --- a/packages/frontend/src/pages/about.overview.vue +++ b/packages/frontend/src/pages/about.overview.vue @@ -170,9 +170,9 @@ await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.pu <style lang="scss" module> .banner { text-align: center; - border-radius: var(--radius); + border-radius: var(--MI-radius); overflow: clip; - background-color: var(--panel); + background-color: var(--MI_THEME-panel); background-size: cover; background-position: center center; } @@ -181,7 +181,7 @@ await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.pu display: block; margin: 16px auto 0 auto; max-height: 96px; - border-radius: var(--radius-sm);; + border-radius: var(--MI-radius-sm);; } .bannerName { @@ -208,19 +208,19 @@ await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.pu flex-shrink: 0; display: flex; position: sticky; - top: calc(var(--stickyTop, 0px) + 8px); + top: calc(var(--MI-stickyTop, 0px) + 8px); counter-increment: item; content: counter(item); width: 32px; height: 32px; line-height: 32px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); font-size: 13px; font-weight: bold; align-items: center; justify-content: center; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } } @@ -238,23 +238,23 @@ await misskeyApi('sponsors', { instance: true }).then((res) => sponsors.value.pu display: flex; align-items: center; padding: 12px; - background: var(--buttonBg); - border-radius: var(--radius-sm); + background: var(--MI_THEME-buttonBg); + border-radius: var(--MI-radius-sm); &:hover { text-decoration: none; - background: var(--buttonHoverBg); + background: var(--MI_THEME-buttonHoverBg); } &.active { - color: var(--accent); - background: var(--buttonHoverBg); + color: var(--MI_THEME-accent); + background: var(--MI_THEME-buttonHoverBg); } } .contributorAvatar { width: 30px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .contributorUsername { diff --git a/packages/frontend/src/pages/admin-user.vue b/packages/frontend/src/pages/admin-user.vue index 22e16effe0..11a34d34ef 100644 --- a/packages/frontend/src/pages/admin-user.vue +++ b/packages/frontend/src/pages/admin-user.vue @@ -55,6 +55,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkTextarea v-model="moderationNote" manualSave> <template #label>{{ i18n.ts.moderationNote }}</template> + <template #caption>{{ i18n.ts.moderationNoteDescription }}</template> </MkTextarea> <FormSection v-if="user.host"> @@ -140,15 +141,21 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-else-if="tab === 'announcements'" class="_gaps"> <MkButton primary rounded @click="createAnnouncement"><i class="ti ti-plus"></i> {{ i18n.ts._announcement.new }}</MkButton> + <MkSelect v-model="announcementsStatus"> + <template #label>{{ i18n.ts.filter }}</template> + <option value="active">{{ i18n.ts.active }}</option> + <option value="archived">{{ i18n.ts.archived }}</option> + </MkSelect> + <MkPagination :pagination="announcementsPagination"> <template #default="{ items }"> <div class="_gaps_s"> <div v-for="announcement in items" :key="announcement.id" v-panel :class="$style.announcementItem" @click="editAnnouncement(announcement)"> <span style="margin-right: 0.5em;"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </span> <span>{{ announcement.title }}</span> <span v-if="announcement.reads > 0" style="margin-left: auto; opacity: 0.7;">{{ i18n.ts.messageRead }}</span> @@ -193,6 +200,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, defineAsyncComponent, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; import MkChart from '@/components/MkChart.vue'; import MkObjectView from '@/components/MkObjectView.vue'; import MkTextarea from '@/components/MkTextarea.vue'; @@ -208,7 +216,6 @@ import MkFileListForAdmin from '@/components/MkFileListForAdmin.vue'; import MkInfo from '@/components/MkInfo.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { url } from '@@/js/config.js'; import { acct } from '@/filters/user.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; @@ -244,11 +251,15 @@ const filesPagination = { userId: props.userId, })), }; + +const announcementsStatus = ref<'active' | 'archived'>('active'); + const announcementsPagination = { endpoint: 'admin/announcements/list' as const, limit: 10, params: computed(() => ({ userId: props.userId, + status: announcementsStatus.value, })), }; const expandedRoles = ref([]); @@ -594,24 +605,24 @@ definePageMetadata(() => ({ > .suspended, > .silenced, > .moderator { display: inline-block; border: solid 1px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); padding: 2px 6px; font-size: 85%; } > .suspended { - color: var(--error); - border-color: var(--error); + color: var(--MI_THEME-error); + border-color: var(--MI_THEME-error); } > .silenced { - color: var(--warn); - border-color: var(--warn); + color: var(--MI_THEME-warn); + border-color: var(--MI_THEME-warn); } > .moderator { - color: var(--success); - border-color: var(--success); + color: var(--MI_THEME-success); + border-color: var(--MI_THEME-success); } } } @@ -633,13 +644,13 @@ definePageMetadata(() => ({ .casdwq { .silenced { - color: var(--warn); - border-color: var(--warn); + color: var(--MI_THEME-warn); + border-color: var(--MI_THEME-warn); } .moderator { - color: var(--success); - border-color: var(--success); + color: var(--MI_THEME-success); + border-color: var(--MI_THEME-success); } } </style> @@ -647,6 +658,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .ip { display: flex; + word-break: break-all; > :global(.date) { opacity: 0.7; @@ -670,7 +682,7 @@ definePageMetadata(() => ({ .roleItemSub { padding: 6px 12px; font-size: 85%; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } .roleUnassign { @@ -683,7 +695,7 @@ definePageMetadata(() => ({ .announcementItem { display: flex; padding: 8px 12px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); cursor: pointer; } </style> diff --git a/packages/frontend/src/pages/admin/RolesEditorFormula.vue b/packages/frontend/src/pages/admin/RolesEditorFormula.vue index f001a4ac20..4762ef3f97 100644 --- a/packages/frontend/src/pages/admin/RolesEditorFormula.vue +++ b/packages/frontend/src/pages/admin/RolesEditorFormula.vue @@ -155,12 +155,12 @@ function removeSelf() { } .item { - border: solid 2px var(--divider); - border-radius: var(--radius); + border: solid 2px var(--MI_THEME-divider); + border-radius: var(--MI-radius); padding: 12px; &:hover { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } } </style> diff --git a/packages/frontend/src/pages/admin/_header_.vue b/packages/frontend/src/pages/admin/_header_.vue index 2a71e3efab..b0651150a6 100644 --- a/packages/frontend/src/pages/admin/_header_.vue +++ b/packages/frontend/src/pages/admin/_header_.vue @@ -119,7 +119,7 @@ function onTabClick(tab: Tab, ev: MouseEvent): void { } const calcBg = () => { - const rawBg = pageMetadata.value?.bg ?? 'var(--bg)'; + const rawBg = pageMetadata.value?.bg ?? 'var(--MI_THEME-bg)'; const tinyBg = tinycolor(rawBg.startsWith('var(') ? getComputedStyle(document.documentElement).getPropertyValue(rawBg.slice(4, -1)) : rawBg); tinyBg.setAlpha(0.85); bg.value = tinyBg.toRgbString(); @@ -156,8 +156,8 @@ onUnmounted(() => { --height: 60px; display: flex; width: 100%; - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); > .buttons { --margin: 8px; @@ -182,14 +182,14 @@ onUnmounted(() => { width: calc(var(--height) - (var(--margin) * 2)); box-sizing: border-box; position: relative; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); &:hover { background: rgba(0, 0, 0, 0.05); } &.highlighted { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -286,8 +286,8 @@ onUnmounted(() => { position: absolute; bottom: 0; height: 3px; - background: var(--accent); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-accent); + border-radius: var(--MI-radius-ellipse); transition: all 0.2s ease; pointer-events: none; } diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue index 827e22e8ae..eef24afd32 100644 --- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.editor.vue @@ -294,10 +294,10 @@ onMounted(async () => { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } .systemWebhook { diff --git a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue index 0b86808faf..36d586bd23 100644 --- a/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue +++ b/packages/frontend/src/pages/admin/abuse-report/notification-recipient.item.vue @@ -87,7 +87,7 @@ function onDeleteButtonClicked() { } .rightDivider { - border-right: 0.5px solid var(--divider); + border-right: 0.5px solid var(--MI_THEME-divider); } .recipientButtons { @@ -108,7 +108,7 @@ function onDeleteButtonClicked() { padding: 8px; &:hover { - background-color: var(--buttonBg); + background-color: var(--MI_THEME-buttonBg); } } </style> diff --git a/packages/frontend/src/pages/admin/abuses.vue b/packages/frontend/src/pages/admin/abuses.vue index c8a9ca7112..a164ecb1fe 100644 --- a/packages/frontend/src/pages/admin/abuses.vue +++ b/packages/frontend/src/pages/admin/abuses.vue @@ -12,6 +12,10 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton link to="/admin/abuse-report-notification-recipient" primary>{{ i18n.ts.notificationSetting }}</MkButton> </div> + <MkInfo v-if="!defaultStore.reactiveState.abusesTutorial.value" closable @close="closeTutorial()"> + {{ i18n.ts._abuseUserReport.resolveTutorial }} + </MkInfo> + <div :class="$style.inputs" class="_gaps"> <MkSelect v-model="state" style="margin: 0; flex: 1;"> <template #label>{{ i18n.ts.state }}</template> @@ -44,8 +48,10 @@ SPDX-License-Identifier: AGPL-3.0-only </div> --> - <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);"> - <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> + <MkPagination v-slot="{items}" ref="reports" :pagination="pagination" :displayLimit="50"> + <div class="_gaps"> + <XAbuseReport v-for="report in items" :key="report.id" :report="report" @resolved="resolved"/> + </div> </MkPagination> </div> </MkSpacer> @@ -54,7 +60,6 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, shallowRef, ref } from 'vue'; - import XHeader from './_header_.vue'; import MkSelect from '@/components/MkSelect.vue'; import MkPagination from '@/components/MkPagination.vue'; @@ -62,6 +67,8 @@ import XAbuseReport from '@/components/MkAbuseReport.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkButton from '@/components/MkButton.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import { defaultStore } from '@/store.js'; const reports = shallowRef<InstanceType<typeof MkPagination>>(); @@ -85,6 +92,10 @@ function resolved(reportId) { reports.value?.removeItem(reportId); } +function closeTutorial() { + defaultStore.set('abusesTutorial', false); +} + const headerActions = computed(() => []); const headerTabs = computed(() => []); diff --git a/packages/frontend/src/pages/admin/ads.vue b/packages/frontend/src/pages/admin/ads.vue index 6c8901b10b..0d67359e47 100644 --- a/packages/frontend/src/pages/admin/ads.vue +++ b/packages/frontend/src/pages/admin/ads.vue @@ -266,7 +266,7 @@ definePageMetadata(() => ({ padding: 32px; &:not(:last-child) { - margin-bottom: var(--margin); + margin-bottom: var(--MI-margin); } } .input { diff --git a/packages/frontend/src/pages/admin/announcements.vue b/packages/frontend/src/pages/admin/announcements.vue index fd37311b21..e420586017 100644 --- a/packages/frontend/src/pages/admin/announcements.vue +++ b/packages/frontend/src/pages/admin/announcements.vue @@ -24,9 +24,9 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ announcement.title }}</template> <template #icon> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </template> <template #caption>{{ announcement.text }}</template> <template #footer> @@ -51,9 +51,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkRadios v-model="announcement.icon"> <template #label>{{ i18n.ts.icon }}</template> <option value="info"><i class="ti ti-info-circle"></i></option> - <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--warn);"></i></option> - <option value="error"><i class="ti ti-circle-x" style="color: var(--error);"></i></option> - <option value="success"><i class="ti ti-check" style="color: var(--success);"></i></option> + <option value="warning"><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i></option> + <option value="error"><i class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i></option> + <option value="success"><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i></option> </MkRadios> <MkRadios v-model="announcement.display"> <template #label>{{ i18n.ts.display }}</template> diff --git a/packages/frontend/src/pages/admin/bot-protection.vue b/packages/frontend/src/pages/admin/bot-protection.vue index 644436cde6..2f6dac8097 100644 --- a/packages/frontend/src/pages/admin/bot-protection.vue +++ b/packages/frontend/src/pages/admin/bot-protection.vue @@ -12,6 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template v-else-if="botProtectionForm.savedState.provider === 'recaptcha'" #suffix>reCAPTCHA</template> <template v-else-if="botProtectionForm.savedState.provider === 'turnstile'" #suffix>Turnstile</template> <template v-else-if="botProtectionForm.savedState.provider === 'fc'" #suffix>FriendlyCaptcha</template> + <template v-else-if="botProtectionForm.savedState.provider === 'testcaptcha'" #suffix>testCaptcha</template> <template v-else #suffix>{{ i18n.ts.none }} ({{ i18n.ts.notRecommended }})</template> <template v-if="botProtectionForm.modified.value" #footer> <MkFormFooter :form="botProtectionForm"/> @@ -25,6 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only <option value="recaptcha">reCAPTCHA</option> <option value="turnstile">Turnstile</option> <option value="fc">FriendlyCaptcha</option> + <option value="testcaptcha">testCaptcha</option> </MkRadios> <template v-if="botProtectionForm.state.provider === 'hcaptcha'"> @@ -101,6 +103,13 @@ SPDX-License-Identifier: AGPL-3.0-only <MkCaptcha provider="fc" :sitekey="botProtectionForm.state.fcSiteKey"/> </FormSlot> </template> + <template v-else-if="botProtectionForm.state.provider === 'testcaptcha'"> + <MkInfo warn><span v-html="i18n.ts.testCaptchaWarning"></span></MkInfo> + <FormSlot> + <template #label>{{ i18n.ts.preview }}</template> + <MkCaptcha provider="testcaptcha"/> + </FormSlot> + </template> </div> </MkFolder> </template> @@ -117,6 +126,7 @@ import { i18n } from '@/i18n.js'; import { useForm } from '@/scripts/use-form.js'; import MkFormFooter from '@/components/MkFormFooter.vue'; import MkFolder from '@/components/MkFolder.vue'; +import MkInfo from '@/components/MkInfo.vue'; const MkCaptcha = defineAsyncComponent(() => import('@/components/MkCaptcha.vue')); @@ -133,6 +143,8 @@ const botProtectionForm = useForm({ ? 'mcaptcha' : meta.enableFC ? 'fc' + : meta.enableTestcaptcha + ? 'testcaptcha' : null, hcaptchaSiteKey: meta.hcaptchaSiteKey, hcaptchaSecretKey: meta.hcaptchaSecretKey, @@ -163,6 +175,7 @@ const botProtectionForm = useForm({ enableFC: state.provider === 'fc', fcSiteKey: state.fcSiteKey, fcSecretKey: state.fcSecretKey, + enableTestcaptcha: state.provider === 'testcaptcha', }); fetchInstance(true); }); diff --git a/packages/frontend/src/pages/admin/branding.vue b/packages/frontend/src/pages/admin/branding.vue index d3d52002fe..cc05466832 100644 --- a/packages/frontend/src/pages/admin/branding.vue +++ b/packages/frontend/src/pages/admin/branding.vue @@ -218,7 +218,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/admin/email-settings.vue b/packages/frontend/src/pages/admin/email-settings.vue index ddfe5ae81f..2f1d12ebb5 100644 --- a/packages/frontend/src/pages/admin/email-settings.vue +++ b/packages/frontend/src/pages/admin/email-settings.vue @@ -138,7 +138,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/admin/federation.vue b/packages/frontend/src/pages/admin/federation.vue index 1902c97724..ef6bbb865b 100644 --- a/packages/frontend/src/pages/admin/federation.vue +++ b/packages/frontend/src/pages/admin/federation.vue @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #prefix><i class="ti ti-search"></i></template> <template #label>{{ i18n.ts.host }}</template> </MkInput> - <FormSplit style="margin-top: var(--margin);"> + <FormSplit style="margin-top: var(--MI-margin);"> <MkSelect v-model="state"> <template #label>{{ i18n.ts.state }}</template> <option value="all">{{ i18n.ts.all }}</option> diff --git a/packages/frontend/src/pages/admin/files.vue b/packages/frontend/src/pages/admin/files.vue index 5132b85c64..4cc859227f 100644 --- a/packages/frontend/src/pages/admin/files.vue +++ b/packages/frontend/src/pages/admin/files.vue @@ -9,7 +9,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><XHeader :actions="headerActions"/></template> <MkSpacer :contentMax="900"> <div class="_gaps"> - <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <div class="inputs" style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;"> <MkSelect v-model="origin" style="margin: 0; flex: 1;"> <template #label>{{ i18n.ts.instance }}</template> <option value="combined">{{ i18n.ts.all }}</option> @@ -20,7 +20,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #label>{{ i18n.ts.host }}</template> </MkInput> </div> - <div class="inputs" style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <div class="inputs" style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;"> <MkInput v-model="userId" :debounce="true" type="search" style="margin: 0; flex: 1;"> <template #label>User ID</template> </MkInput> diff --git a/packages/frontend/src/pages/admin/index.vue b/packages/frontend/src/pages/admin/index.vue index d6cd1f0fb1..6cdf0eda7a 100644 --- a/packages/frontend/src/pages/admin/index.vue +++ b/packages/frontend/src/pages/admin/index.vue @@ -15,7 +15,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps_s"> <MkInfo v-if="thereIsUnresolvedAbuseReport" warn class="info">{{ i18n.ts.thereIsUnresolvedAbuseReportWarning }} <MkA to="/admin/abuses" class="_link">{{ i18n.ts.check }}</MkA></MkInfo> <MkInfo v-if="noMaintainerInformation" warn class="info">{{ i18n.ts.noMaintainerInformationWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> - <MkInfo v-if="noInquiryUrl" warn>{{ i18n.ts.noInquiryUrlWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> + <MkInfo v-if="noInquiryUrl" warn class="info">{{ i18n.ts.noInquiryUrlWarning }} <MkA to="/admin/settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> <MkInfo v-if="noBotProtection" warn class="info">{{ i18n.ts.noBotProtectionWarning }} <MkA to="/admin/security" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> <MkInfo v-if="noEmailServer" warn class="info">{{ i18n.ts.noEmailServerWarning }} <MkA to="/admin/email-settings" class="_link">{{ i18n.ts.configure }}</MkA></MkInfo> <MkInfo v-if="pendingUserApprovals" warn class="info">{{ i18n.ts.pendingUserApprovals }} <MkA to="/admin/approvals" class="_link">{{ i18n.ts.check }}</MkA></MkInfo> @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSpacer> </div> <div v-if="!(narrow && currentPage?.route.name == null)" class="main"> - <RouterView/> + <RouterView nested/> </div> </div> </template> @@ -346,7 +346,7 @@ defineExpose({ width: 32%; max-width: 280px; box-sizing: border-box; - border-right: solid 0.5px var(--divider); + border-right: solid 0.5px var(--MI_THEME-divider); overflow: auto; height: 100%; } @@ -366,7 +366,7 @@ defineExpose({ display: block; margin: auto; height: 42px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } } } diff --git a/packages/frontend/src/pages/admin/moderation.vue b/packages/frontend/src/pages/admin/moderation.vue index bbcf2a6f77..27cbfda078 100644 --- a/packages/frontend/src/pages/admin/moderation.vue +++ b/packages/frontend/src/pages/admin/moderation.vue @@ -10,8 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="700" :marginMin="16" :marginMax="32"> <FormSuspense :p="init"> <div class="_gaps_m"> - <MkSwitch v-model="enableRegistration" @change="onChange_enableRegistration"> - <template #label>{{ i18n.ts.enableRegistration }}</template> + <MkSwitch :modelValue="enableRegistration" @update:modelValue="onChange_enableRegistration"> + <template #label>{{ i18n.ts._serverSettings.openRegistration }}</template> + <template #caption> + <div>{{ i18n.ts._serverSettings.thisSettingWillAutomaticallyOffWhenModeratorsInactive }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._serverSettings.openRegistrationWarning }}</div> + </template> </MkSwitch> <MkSwitch v-model="emailRequiredForSignup" @change="onChange_emailRequiredForSignup"> @@ -85,6 +89,18 @@ SPDX-License-Identifier: AGPL-3.0-only </MkFolder> <MkFolder> + <template #icon><i class="ti ti-user-x"></i></template> + <template #label>{{ i18n.ts.prohibitedWordsForNameOfUser }}</template> + + <div class="_gaps"> + <MkTextarea v-model="prohibitedWordsForNameOfUser"> + <template #caption>{{ i18n.ts.prohibitedWordsForNameOfUserDescription }}<br>{{ i18n.ts.prohibitedWordsDescription2 }}</template> + </MkTextarea> + <MkButton primary @click="save_prohibitedWordsForNameOfUser">{{ i18n.ts.save }}</MkButton> + </div> + </MkFolder> + + <MkFolder> <template #icon><i class="ti ti-eye-off"></i></template> <template #label>{{ i18n.ts.hiddenTags }}</template> @@ -160,6 +176,7 @@ const approvalRequiredForSignup = ref<boolean>(false); const bubbleTimelineEnabled = ref<boolean>(false); const sensitiveWords = ref<string>(''); const prohibitedWords = ref<string>(''); +const prohibitedWordsForNameOfUser = ref<string>(''); const hiddenTags = ref<string>(''); const preservedUsernames = ref<string>(''); const bubbleTimeline = ref<string>(''); @@ -175,17 +192,28 @@ async function init() { approvalRequiredForSignup.value = meta.approvalRequiredForSignup; sensitiveWords.value = meta.sensitiveWords.join('\n'); prohibitedWords.value = meta.prohibitedWords.join('\n'); + prohibitedWordsForNameOfUser.value = meta.prohibitedWordsForNameOfUser.join('\n'); hiddenTags.value = meta.hiddenTags.join('\n'); preservedUsernames.value = meta.preservedUsernames.join('\n'); bubbleTimeline.value = meta.bubbleInstances.join('\n'); bubbleTimelineEnabled.value = meta.policies.btlAvailable; trustedLinkUrlPatterns.value = meta.trustedLinkUrlPatterns.join('\n'); blockedHosts.value = meta.blockedHosts.join('\n'); - silencedHosts.value = meta.silencedHosts.join('\n'); + silencedHosts.value = meta.silencedHosts?.join('\n') ?? ''; mediaSilencedHosts.value = meta.mediaSilencedHosts.join('\n'); } -function onChange_enableRegistration(value: boolean) { +async function onChange_enableRegistration(value: boolean) { + if (value) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.acknowledgeNotesAndEnable, + }); + if (canceled) return; + } + + enableRegistration.value = value; + os.apiWithDialog('admin/update-meta', { disableRegistration: !value, }).then(() => { @@ -249,6 +277,14 @@ function save_prohibitedWords() { }); } +function save_prohibitedWordsForNameOfUser() { + os.apiWithDialog('admin/update-meta', { + prohibitedWordsForNameOfUser: prohibitedWordsForNameOfUser.value.split('\n'), + }).then(() => { + fetchInstance(true); + }); +} + function save_hiddenTags() { os.apiWithDialog('admin/update-meta', { hiddenTags: hiddenTags.value.split('\n'), diff --git a/packages/frontend/src/pages/admin/modlog.ModLog.vue b/packages/frontend/src/pages/admin/modlog.ModLog.vue index 6c81155c51..37a9cc83e7 100644 --- a/packages/frontend/src/pages/admin/modlog.ModLog.vue +++ b/packages/frontend/src/pages/admin/modlog.ModLog.vue @@ -101,7 +101,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <div> - <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <div style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;"> <div style="flex: 1;">{{ i18n.ts.moderator }}: <MkA :to="`/admin/user/${log.userId}`" class="_link">@{{ log.user?.username }}</MkA></div> <div style="flex: 1;">{{ i18n.ts.dateAndTime }}: <MkTime :time="log.createdAt" mode="detail"/></div> </div> @@ -180,6 +180,11 @@ SPDX-License-Identifier: AGPL-3.0-only <CodeDiff :context="5" :hideHeader="true" :oldString="JSON5.stringify(log.info.before, null, '\t')" :newString="JSON5.stringify(log.info.after, null, '\t')" language="javascript" maxHeight="300px"/> </div> </template> + <template v-else-if="log.type === 'updateAbuseReportNote'"> + <div :class="$style.diff"> + <CodeDiff :context="5" :hideHeader="true" :oldString="log.info.before ?? ''" :newString="log.info.after ?? ''" maxHeight="300px"/> + </div> + </template> <details> <summary>raw</summary> @@ -210,19 +215,19 @@ const props = defineProps<{ .diff { background: #fff; color: #000; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; } .logYellow { - color: var(--warn); + color: var(--MI_THEME-warn); } .logRed { - color: var(--error); + color: var(--MI_THEME-error); } .logGreen { - color: var(--success); + color: var(--MI_THEME-success); } </style> diff --git a/packages/frontend/src/pages/admin/modlog.vue b/packages/frontend/src/pages/admin/modlog.vue index 4e0d0f941e..35f939f1be 100644 --- a/packages/frontend/src/pages/admin/modlog.vue +++ b/packages/frontend/src/pages/admin/modlog.vue @@ -8,7 +8,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><XHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="900"> <div> - <div style="display: flex; gap: var(--margin); flex-wrap: wrap;"> + <div style="display: flex; gap: var(--MI-margin); flex-wrap: wrap;"> <MkSelect v-model="type" style="margin: 0; flex: 1;"> <template #label>{{ i18n.ts.type }}</template> <option :value="null">{{ i18n.ts.all }}</option> @@ -19,10 +19,10 @@ SPDX-License-Identifier: AGPL-3.0-only </MkInput> </div> - <MkPagination v-slot="{items}" ref="logs" :pagination="pagination" :displayLimit="50" style="margin-top: var(--margin);"> - <div class="_gaps_s"> - <XModLog v-for="item in items" :key="item.id" :log="item"/> - </div> + <MkPagination v-slot="{items}" ref="logs" :pagination="pagination" :displayLimit="50" style="margin-top: var(--MI-margin);"> + <MkDateSeparatedList v-slot="{ item }" :items="items" :noGap="false" style="--MI-margin: 8px;"> + <XModLog :key="item.id" :log="item"/> + </MkDateSeparatedList> </MkPagination> </div> </MkSpacer> @@ -39,6 +39,7 @@ import MkInput from '@/components/MkInput.vue'; import MkPagination from '@/components/MkPagination.vue'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkDateSeparatedList from '@/components/MkDateSeparatedList.vue'; const logs = shallowRef<InstanceType<typeof MkPagination>>(); diff --git a/packages/frontend/src/pages/admin/object-storage.vue b/packages/frontend/src/pages/admin/object-storage.vue index 5fddb715cd..d5a664934c 100644 --- a/packages/frontend/src/pages/admin/object-storage.vue +++ b/packages/frontend/src/pages/admin/object-storage.vue @@ -157,7 +157,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/admin/overview.ap-requests.vue b/packages/frontend/src/pages/admin/overview.ap-requests.vue index 4bbb9210af..570fcddc07 100644 --- a/packages/frontend/src/pages/admin/overview.ap-requests.vue +++ b/packages/frontend/src/pages/admin/overview.ap-requests.vue @@ -278,7 +278,7 @@ onMounted(async () => { padding: 16px; &:first-child { - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); } } } diff --git a/packages/frontend/src/pages/admin/overview.federation.vue b/packages/frontend/src/pages/admin/overview.federation.vue index ea01d073ea..9062c73ff6 100644 --- a/packages/frontend/src/pages/admin/overview.federation.vue +++ b/packages/frontend/src/pages/admin/overview.federation.vue @@ -151,9 +151,9 @@ onMounted(async () => { height: 100%; aspect-ratio: 1; margin-right: 12px; - background: var(--accentedBg); - color: var(--accent); - border-radius: var(--radius); + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + border-radius: var(--MI-radius); } &.sub { diff --git a/packages/frontend/src/pages/admin/overview.pie.vue b/packages/frontend/src/pages/admin/overview.pie.vue index c7a9f2a702..a21ec6c464 100644 --- a/packages/frontend/src/pages/admin/overview.pie.vue +++ b/packages/frontend/src/pages/admin/overview.pie.vue @@ -41,7 +41,7 @@ onMounted(() => { labels: props.data.map(x => x.name), datasets: [{ backgroundColor: props.data.map(x => x.color), - borderColor: getComputedStyle(document.documentElement).getPropertyValue('--panel'), + borderColor: getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-panel'), borderWidth: 2, hoverOffset: 0, data: props.data.map(x => x.value), diff --git a/packages/frontend/src/pages/admin/overview.queue.vue b/packages/frontend/src/pages/admin/overview.queue.vue index fb190f5325..de6b254412 100644 --- a/packages/frontend/src/pages/admin/overview.queue.vue +++ b/packages/frontend/src/pages/admin/overview.queue.vue @@ -119,8 +119,8 @@ onUnmounted(() => { > .chart { min-width: 0; padding: 16px; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); > .title { font-size: 0.85em; diff --git a/packages/frontend/src/pages/admin/overview.stats.vue b/packages/frontend/src/pages/admin/overview.stats.vue index 37399a5724..a967b305f7 100644 --- a/packages/frontend/src/pages/admin/overview.stats.vue +++ b/packages/frontend/src/pages/admin/overview.stats.vue @@ -114,9 +114,9 @@ onMounted(async () => { height: 100%; aspect-ratio: 1; margin-right: 12px; - background: var(--accentedBg); - color: var(--accent); - border-radius: var(--radius); + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); + border-radius: var(--MI-radius); } &.users { diff --git a/packages/frontend/src/pages/admin/performance.vue b/packages/frontend/src/pages/admin/performance.vue index 7e0a932f82..12338f0bf9 100644 --- a/packages/frontend/src/pages/admin/performance.vue +++ b/packages/frontend/src/pages/admin/performance.vue @@ -30,6 +30,13 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div class="_panel" style="padding: 16px;"> + <MkSwitch v-model="enableStatsForFederatedInstances" @change="onChange_enableStatsForFederatedInstances"> + <template #label>{{ i18n.ts.enableStatsForFederatedInstances }}</template> + <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> + </MkSwitch> + </div> + + <div class="_panel" style="padding: 16px;"> <MkSwitch v-model="enableChartsForFederatedInstances" @change="onChange_enableChartsForFederatedInstances"> <template #label>{{ i18n.ts.enableChartsForFederatedInstances }}</template> <template #caption>{{ i18n.ts.turnOffToImprovePerformance }}</template> @@ -120,6 +127,7 @@ const meta = await misskeyApi('admin/meta'); const enableServerMachineStats = ref(meta.enableServerMachineStats); const enableIdenticonGeneration = ref(meta.enableIdenticonGeneration); const enableChartsForRemoteUser = ref(meta.enableChartsForRemoteUser); +const enableStatsForFederatedInstances = ref(meta.enableStatsForFederatedInstances); const enableChartsForFederatedInstances = ref(meta.enableChartsForFederatedInstances); function onChange_enableServerMachineStats(value: boolean) { @@ -146,6 +154,14 @@ function onChange_enableChartsForRemoteUser(value: boolean) { }); } +function onChange_enableStatsForFederatedInstances(value: boolean) { + os.apiWithDialog('admin/update-meta', { + enableStatsForFederatedInstances: value, + }).then(() => { + fetchInstance(true); + }); +} + function onChange_enableChartsForFederatedInstances(value: boolean) { os.apiWithDialog('admin/update-meta', { enableChartsForFederatedInstances: value, diff --git a/packages/frontend/src/pages/admin/queue.chart.vue b/packages/frontend/src/pages/admin/queue.chart.vue index 960a263a86..7c171ba0e1 100644 --- a/packages/frontend/src/pages/admin/queue.chart.vue +++ b/packages/frontend/src/pages/admin/queue.chart.vue @@ -135,8 +135,8 @@ onUnmounted(() => { .chart { min-width: 0; padding: 16px; - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); } .chartTitle { diff --git a/packages/frontend/src/pages/admin/relays.vue b/packages/frontend/src/pages/admin/relays.vue index 04982eea1f..17e99e6593 100644 --- a/packages/frontend/src/pages/admin/relays.vue +++ b/packages/frontend/src/pages/admin/relays.vue @@ -11,8 +11,8 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-for="relay in relays" :key="relay.inbox" class="relaycxt _panel" style="padding: 16px;"> <div>{{ relay.inbox }}</div> <div style="margin: 8px 0;"> - <i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--success);"></i> - <i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--error);"></i> + <i v-if="relay.status === 'accepted'" class="ti ti-check" :class="$style.icon" style="color: var(--MI_THEME-success);"></i> + <i v-else-if="relay.status === 'rejected'" class="ti ti-ban" :class="$style.icon" style="color: var(--MI_THEME-error);"></i> <i v-else class="ti ti-clock" :class="$style.icon"></i> <span>{{ i18n.ts._relayStatus[relay.status] }}</span> </div> diff --git a/packages/frontend/src/pages/admin/roles.edit.vue b/packages/frontend/src/pages/admin/roles.edit.vue index 60f06d50ba..2b4006c3f7 100644 --- a/packages/frontend/src/pages/admin/roles.edit.vue +++ b/packages/frontend/src/pages/admin/roles.edit.vue @@ -95,7 +95,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/admin/roles.role.vue b/packages/frontend/src/pages/admin/roles.role.vue index 6bce60cfb9..d1c7be39d6 100644 --- a/packages/frontend/src/pages/admin/roles.role.vue +++ b/packages/frontend/src/pages/admin/roles.role.vue @@ -184,7 +184,7 @@ definePageMetadata(() => ({ .userItemSub { padding: 6px 12px; font-size: 85%; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } .userItemMainBody { diff --git a/packages/frontend/src/pages/admin/server-rules.vue b/packages/frontend/src/pages/admin/server-rules.vue index 4788c16830..6c1227b3cf 100644 --- a/packages/frontend/src/pages/admin/server-rules.vue +++ b/packages/frontend/src/pages/admin/server-rules.vue @@ -76,7 +76,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .item { display: block; - color: var(--navFg); + color: var(--MI_THEME-navFg); } .itemHeader { @@ -96,15 +96,15 @@ definePageMetadata(() => ({ .itemNumber { display: flex; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); font-size: 14px; font-weight: bold; width: 28px; height: 28px; align-items: center; justify-content: center; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); margin-right: 8px; } @@ -117,12 +117,12 @@ definePageMetadata(() => ({ .itemRemove { width: 40px; height: 40px; - color: var(--error); + color: var(--MI_THEME-error); margin-left: auto; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); &:hover { - background: var(--X5); + background: var(--MI_THEME-X5); } } diff --git a/packages/frontend/src/pages/admin/settings.vue b/packages/frontend/src/pages/admin/settings.vue index 04975dcbf3..68f211de5c 100644 --- a/packages/frontend/src/pages/admin/settings.vue +++ b/packages/frontend/src/pages/admin/settings.vue @@ -438,6 +438,6 @@ definePageMetadata(() => ({ <style lang="scss" module> .subCaption { font-size: 0.85em; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/pages/admin/system-webhook.item.vue b/packages/frontend/src/pages/admin/system-webhook.item.vue index 4e767fba16..45f0fff107 100644 --- a/packages/frontend/src/pages/admin/system-webhook.item.vue +++ b/packages/frontend/src/pages/admin/system-webhook.item.vue @@ -6,15 +6,16 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkFolder> <template #label>{{ entity.name || entity.url }}</template> + <template v-if="entity.name != null && entity.name != ''" #caption>{{ entity.url }}</template> <template #icon> <i v-if="!entity.isActive" class="ti ti-player-pause"/> <i v-else-if="entity.latestStatus === null" class="ti ti-circle"/> <i v-else-if="[200, 201, 204].includes(entity.latestStatus)" class="ti ti-check" - :style="{ color: 'var(--success)' }" + :style="{ color: 'var(--MI_THEME-success)' }" /> - <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"/> + <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"/> </template> <template #suffix> <MkTime v-if="entity.latestSentAt" :time="entity.latestSentAt" style="margin-right: 8px"/> @@ -74,6 +75,6 @@ function onDeleteClick() { margin-right: 0.75em; flex-shrink: 0; text-align: center; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/pages/admin/users.vue b/packages/frontend/src/pages/admin/users.vue index e99dcfa489..e26a2c827e 100644 --- a/packages/frontend/src/pages/admin/users.vue +++ b/packages/frontend/src/pages/admin/users.vue @@ -100,19 +100,19 @@ async function addUser() { const { canceled: canceled1, result: username } = await os.inputText({ title: i18n.ts.username, }); - if (canceled1) return; + if (canceled1 || username == null) return; const { canceled: canceled2, result: password } = await os.inputText({ title: i18n.ts.password, type: 'password', }); - if (canceled2) return; + if (canceled2 || password == null) return; os.apiWithDialog('admin/accounts/create', { username: username, password: password, }).then(res => { - paginationComponent.value.reload(); + paginationComponent.value?.reload(); }); } diff --git a/packages/frontend/src/pages/announcement.vue b/packages/frontend/src/pages/announcement.vue index 802a6bf399..56c10fb292 100644 --- a/packages/frontend/src/pages/announcement.vue +++ b/packages/frontend/src/pages/announcement.vue @@ -20,9 +20,9 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span> <span style="margin-right: 0.5em;"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </span> <Mfm :text="announcement.title"/> </div> @@ -55,7 +55,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { $i, updateAccount } from '@/account.js'; +import { $i, updateAccountPartial } from '@/account.js'; import { defaultStore } from '@/store.js'; const props = defineProps<{ @@ -90,7 +90,7 @@ async function read(target: Misskey.entities.Announcement): Promise<void> { target.isRead = true; await misskeyApi('i/read-announcement', { announcementId: target.id }); if ($i) { - updateAccount({ + updateAccountPartial({ unreadAnnouncements: $i.unreadAnnouncements.filter((a: { id: string; }) => a.id !== target.id), }); } @@ -103,7 +103,7 @@ const headerActions = computed(() => []); const headerTabs = computed(() => []); definePageMetadata(() => ({ - title: announcement.value ? `${i18n.ts.announcements}: ${announcement.value.title}` : i18n.ts.announcements, + title: announcement.value ? announcement.value.title : i18n.ts.announcements, icon: 'ti ti-speakerphone', })); </script> diff --git a/packages/frontend/src/pages/announcements.vue b/packages/frontend/src/pages/announcements.vue index a9bcfee803..79b9e7607d 100644 --- a/packages/frontend/src/pages/announcements.vue +++ b/packages/frontend/src/pages/announcements.vue @@ -17,9 +17,9 @@ SPDX-License-Identifier: AGPL-3.0-only <span v-if="$i && !announcement.silence && !announcement.isRead" style="margin-right: 0.5em;">🆕</span> <span style="margin-right: 0.5em;"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </span> <MkA :to="`/announcements/${announcement.id}`"><span>{{ announcement.title }}</span></MkA> </div> @@ -56,7 +56,7 @@ import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { $i, updateAccount } from '@/account.js'; +import { $i, updateAccountPartial } from '@/account.js'; const paginationCurrent = { endpoint: 'announcements' as const, @@ -94,7 +94,7 @@ async function read(target) { return a; }); misskeyApi('i/read-announcement', { announcementId: target.id }); - updateAccount({ + updateAccountPartial({ unreadAnnouncements: $i!.unreadAnnouncements.filter(a => a.id !== target.id), }); } diff --git a/packages/frontend/src/pages/antenna-timeline.vue b/packages/frontend/src/pages/antenna-timeline.vue index e57e212b60..350f772d65 100644 --- a/packages/frontend/src/pages/antenna-timeline.vue +++ b/packages/frontend/src/pages/antenna-timeline.vue @@ -97,26 +97,26 @@ definePageMetadata(() => ({ <style lang="scss" module> .new { position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); + top: calc(var(--MI-stickyTop, 0px) + 16px); z-index: 1000; width: 100%; margin: calc(-0.675em - 8px) 0; &:first-child { - margin-top: calc(-0.675em - 8px - var(--margin)); + margin-top: calc(-0.675em - 8px - var(--MI-margin)); } } .newButton { display: block; - margin: var(--margin) auto 0 auto; + margin: var(--MI-margin) auto 0 auto; padding: 8px 16px; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); } .tl { - background: var(--bg); - border-radius: var(--radius); + background: var(--MI_THEME-bg); + border-radius: var(--MI-radius); overflow: clip; } </style> diff --git a/packages/frontend/src/pages/auth.vue b/packages/frontend/src/pages/auth.vue index 75a44802f8..30e0e99326 100644 --- a/packages/frontend/src/pages/auth.vue +++ b/packages/frontend/src/pages/auth.vue @@ -83,7 +83,7 @@ function accepted() { location.href = callbackUrl.toString(); } else if (session.value && session.value.app.callbackUrl) { const url = new URL(session.value.app.callbackUrl); - if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(url.protocol)) throw new Error('invalid url'); + if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(url.protocol)) throw new Error('invalid url'); location.href = `${session.value.app.callbackUrl}?token=${session.value.token}`; } } diff --git a/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue new file mode 100644 index 0000000000..a834f1c5fd --- /dev/null +++ b/packages/frontend/src/pages/avatar-decoration-edit-dialog.vue @@ -0,0 +1,220 @@ +<!-- +SPDX-FileCopyrightText: syuilo and misskey-project +SPDX-License-Identifier: AGPL-3.0-only +--> + +<template> +<MkWindow + ref="windowEl" + :initialWidth="400" + :initialHeight="500" + :canResize="true" + @close="windowEl?.close()" + @closed="emit('closed')" +> + <template v-if="avatarDecoration" #header>{{ avatarDecoration.name }}</template> + <template v-else #header>New decoration</template> + + <div style="display: flex; flex-direction: column; min-height: 100%;"> + <MkSpacer :marginMin="20" :marginMax="28" style="flex-grow: 1;"> + <div class="_gaps_m"> + <div :class="$style.preview"> + <div :class="[$style.previewItem, $style.light]"> + <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/> + </div> + <div :class="[$style.previewItem, $style.dark]"> + <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="url != '' ? [{ url }] : []" forceShowDecoration/> + </div> + </div> + <MkInput v-model="name"> + <template #label>{{ i18n.ts.name }}</template> + </MkInput> + <MkInput v-model="url"> + <template #label>{{ i18n.ts.imageUrl }}</template> + </MkInput> + <MkTextarea v-model="description"> + <template #label>{{ i18n.ts.description }}</template> + </MkTextarea> + <MkFolder> + <template #label>{{ i18n.ts.availableRoles }}</template> + <template #suffix>{{ rolesThatCanBeUsedThisDecoration.length === 0 ? i18n.ts.all : rolesThatCanBeUsedThisDecoration.length }}</template> + + <div class="_gaps"> + <MkButton rounded @click="addRole"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + + <div v-for="role in rolesThatCanBeUsedThisDecoration" :key="role.id" :class="$style.roleItem"> + <MkRolePreview :class="$style.role" :role="role" :forModeration="true" :detailed="false" style="pointer-events: none;"/> + <button v-if="role.target === 'manual'" class="_button" :class="$style.roleUnassign" @click="removeRole(role, $event)"><i class="ti ti-x"></i></button> + <button v-else class="_button" :class="$style.roleUnassign" disabled><i class="ti ti-ban"></i></button> + </div> + </div> + </MkFolder> + <MkButton v-if="avatarDecoration" danger @click="del()"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </div> + </MkSpacer> + <div :class="$style.footer"> + <MkButton primary rounded style="margin: 0 auto;" @click="done"><i class="ti ti-check"></i> {{ props.avatarDecoration ? i18n.ts.update : i18n.ts.create }}</MkButton> + </div> + </div> +</MkWindow> +</template> + +<script lang="ts" setup> +import { computed, watch, ref } from 'vue'; +import * as Misskey from 'misskey-js'; +import MkWindow from '@/components/MkWindow.vue'; +import MkButton from '@/components/MkButton.vue'; +import MkInput from '@/components/MkInput.vue'; +import MkInfo from '@/components/MkInfo.vue'; +import MkFolder from '@/components/MkFolder.vue'; +import * as os from '@/os.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; +import { i18n } from '@/i18n.js'; +import MkSwitch from '@/components/MkSwitch.vue'; +import MkRolePreview from '@/components/MkRolePreview.vue'; +import MkTextarea from '@/components/MkTextarea.vue'; +import { signinRequired } from '@/account.js'; + +const $i = signinRequired(); + +const props = defineProps<{ + avatarDecoration?: any, +}>(); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, + (ev: 'closed'): void +}>(); + +const windowEl = ref<InstanceType<typeof MkWindow> | null>(null); +const url = ref<string>(props.avatarDecoration ? props.avatarDecoration.url : ''); +const name = ref<string>(props.avatarDecoration ? props.avatarDecoration.name : ''); +const description = ref<string>(props.avatarDecoration ? props.avatarDecoration.description : ''); +const roleIdsThatCanBeUsedThisDecoration = ref(props.avatarDecoration ? props.avatarDecoration.roleIdsThatCanBeUsedThisDecoration : []); +const rolesThatCanBeUsedThisDecoration = ref<Misskey.entities.Role[]>([]); + +watch(roleIdsThatCanBeUsedThisDecoration, async () => { + rolesThatCanBeUsedThisDecoration.value = (await Promise.all(roleIdsThatCanBeUsedThisDecoration.value.map((id) => misskeyApi('admin/roles/show', { roleId: id }).catch(() => null)))).filter(x => x != null); +}, { immediate: true }); + +async function addRole() { + const roles = await misskeyApi('admin/roles/list'); + const currentRoleIds = rolesThatCanBeUsedThisDecoration.value.map(x => x.id); + + const { canceled, result: role } = await os.select({ + items: roles.filter(r => r.isPublic).filter(r => !currentRoleIds.includes(r.id)).map(r => ({ text: r.name, value: r })), + }); + if (canceled || role == null) return; + + rolesThatCanBeUsedThisDecoration.value.push(role); +} + +async function removeRole(role, ev) { + rolesThatCanBeUsedThisDecoration.value = rolesThatCanBeUsedThisDecoration.value.filter(x => x.id !== role.id); +} + +async function done() { + const params = { + url: url.value, + name: name.value, + description: description.value, + roleIdsThatCanBeUsedThisDecoration: rolesThatCanBeUsedThisDecoration.value.map(x => x.id), + }; + + if (props.avatarDecoration) { + await os.apiWithDialog('admin/avatar-decorations/update', { + id: props.avatarDecoration.id, + ...params, + }); + + emit('done', { + updated: { + id: props.avatarDecoration.id, + ...params, + }, + }); + + windowEl.value?.close(); + } else { + const created = await os.apiWithDialog('admin/avatar-decorations/create', params); + + emit('done', { + created: created, + }); + + windowEl.value?.close(); + } +} + +async function del() { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.tsx.removeAreYouSure({ x: name.value }), + }); + if (canceled) return; + + misskeyApi('admin/avatar-decorations/delete', { + id: props.avatarDecoration.id, + }).then(() => { + emit('done', { + deleted: true, + }); + windowEl.value?.close(); + }); +} +</script> + +<style lang="scss" module> +.preview { + display: grid; + place-items: center; + grid-template-columns: 1fr 1fr; + grid-template-rows: 1fr; + gap: var(--MI-margin); +} + +.previewItem { + width: 100%; + height: 100%; + min-height: 160px; + display: flex; + align-items: center; + justify-content: center; + border-radius: var(--MI-radius); + + &.light { + background: #eee; + } + + &.dark { + background: #222; + } +} + +.roleItem { + display: flex; +} + +.role { + flex: 1; +} + +.roleUnassign { + width: 32px; + height: 32px; + margin-left: 8px; + align-self: center; +} + +.footer { + position: sticky; + z-index: 10000; + bottom: 0; + left: 0; + padding: 12px; + border-top: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); +} +</style> diff --git a/packages/frontend/src/pages/avatar-decorations.vue b/packages/frontend/src/pages/avatar-decorations.vue index b377314856..a5cafb1678 100644 --- a/packages/frontend/src/pages/avatar-decorations.vue +++ b/packages/frontend/src/pages/avatar-decorations.vue @@ -5,92 +5,38 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkStickyContainer> - <template #header><MkPageHeader v-model:tab="tab" :actions="headerActions" :tabs="headerTabs"/></template> + <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="900"> <div class="_gaps"> - <MkFolder v-for="avatarDecoration in avatarDecorations" :key="avatarDecoration.id ?? avatarDecoration._id" :defaultOpen="avatarDecoration.id == null"> - <template #label>{{ avatarDecoration.name }}</template> - <template #caption>{{ avatarDecoration.description }}</template> - - <div :class="$style.editorRoot"> - <div :class="$style.editorWrapper"> - <div :class="$style.preview"> - <div :class="[$style.previewItem, $style.light]"> - <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/> - </div> - <div :class="[$style.previewItem, $style.dark]"> - <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[avatarDecoration]" forceShowDecoration/> - </div> - </div> - <div class="_gaps_m"> - <MkInput v-model="avatarDecoration.name"> - <template #label>{{ i18n.ts.name }}</template> - </MkInput> - <MkTextarea v-model="avatarDecoration.description"> - <template #label>{{ i18n.ts.description }}</template> - </MkTextarea> - <MkInput v-model="avatarDecoration.url"> - <template #label>{{ i18n.ts.imageUrl }}</template> - </MkInput> - <div class="_buttons"> - <MkButton inline primary @click="save(avatarDecoration)"><i class="ti ti-device-floppy"></i> {{ i18n.ts.save }}</MkButton> - <MkButton v-if="avatarDecoration.id != null" inline danger @click="del(avatarDecoration)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> - </div> - </div> - </div> + <div :class="$style.decorations"> + <div + v-for="avatarDecoration in avatarDecorations" + :key="avatarDecoration.id" + v-panel + :class="$style.decoration" + @click="edit(avatarDecoration)" + > + <div :class="$style.decorationName"><MkCondensedLine :minScale="0.5">{{ avatarDecoration.name }}</MkCondensedLine></div> + <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: avatarDecoration.url }]" forceShowDecoration/> </div> - </MkFolder> + </div> </div> </MkSpacer> </MkStickyContainer> </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; +import { ref, computed, defineAsyncComponent } from 'vue'; import * as Misskey from 'misskey-js'; -import MkButton from '@/components/MkButton.vue'; -import MkInput from '@/components/MkInput.vue'; -import MkTextarea from '@/components/MkTextarea.vue'; import { signinRequired } from '@/account.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import MkFolder from '@/components/MkFolder.vue'; - -const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]); const $i = signinRequired(); -function add() { - avatarDecorations.value.unshift({ - _id: Math.random().toString(36), - id: null, - name: '', - description: '', - url: '', - }); -} - -function del(avatarDecoration) { - os.confirm({ - type: 'warning', - text: i18n.tsx.deleteAreYouSure({ x: avatarDecoration.name }), - }).then(({ canceled }) => { - if (canceled) return; - avatarDecorations.value = avatarDecorations.value.filter(x => x !== avatarDecoration); - misskeyApi('admin/avatar-decorations/delete', avatarDecoration); - }); -} - -async function save(avatarDecoration) { - if (avatarDecoration.id == null) { - await os.apiWithDialog('admin/avatar-decorations/create', avatarDecoration); - load(); - } else { - os.apiWithDialog('admin/avatar-decorations/update', avatarDecoration); - } -} +const avatarDecorations = ref<Misskey.entities.AdminAvatarDecorationsListResponse>([]); function load() { misskeyApi('admin/avatar-decorations/list').then(_avatarDecorations => { @@ -100,6 +46,37 @@ function load() { load(); +async function add(ev: MouseEvent) { + const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), { + }, { + done: result => { + if (result.created) { + avatarDecorations.value.unshift(result.created); + } + }, + closed: () => dispose(), + }); +} + +function edit(avatarDecoration) { + const { dispose } = os.popup(defineAsyncComponent(() => import('./avatar-decoration-edit-dialog.vue')), { + avatarDecoration: avatarDecoration, + }, { + done: result => { + if (result.updated) { + const index = avatarDecorations.value.findIndex(x => x.id === avatarDecoration.id); + avatarDecorations.value[index] = { + ...avatarDecorations.value[index], + ...result.updated, + }; + } else if (result.deleted) { + avatarDecorations.value = avatarDecorations.value.filter(x => x.id !== avatarDecoration.id); + } + }, + closed: () => dispose(), + }); +} + const headerActions = computed(() => [{ asFullButton: true, icon: 'ti ti-plus', @@ -116,53 +93,26 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> -.editorRoot { - container: editor / inline-size; -} - -.editorWrapper { +.decorations { display: grid; - grid-template-columns: 1fr; - grid-template-rows: auto auto; - gap: var(--margin); + grid-template-columns: repeat(auto-fill, minmax(140px, 1fr)); + grid-gap: 12px; } -.preview { - display: grid; - place-items: center; - grid-template-columns: 1fr 1fr; - grid-template-rows: 1fr; - gap: var(--margin); -} - -.previewItem { - width: 100%; - height: 100%; - min-height: 160px; - display: flex; - align-items: center; - justify-content: center; - border-radius: var(--radius); - - &.light { - background: #eee; - } - - &.dark { - background: #222; - } +.decoration { + cursor: pointer; + padding: 16px 16px 28px 16px; + border-radius: 8px; + text-align: center; + font-size: 90%; + overflow: clip; + contain: content; } -@container editor (min-width: 600px) { - .editorWrapper { - grid-template-columns: 200px 1fr; - grid-template-rows: 1fr; - gap: calc(var(--margin) * 2); - } - - .preview { - grid-template-columns: 1fr; - grid-template-rows: 1fr 1fr; - } +.decorationName { + position: relative; + z-index: 10; + font-weight: bold; + margin-bottom: 20px; } </style> diff --git a/packages/frontend/src/pages/channel-editor.vue b/packages/frontend/src/pages/channel-editor.vue index d3f4a65b89..6d8274a55c 100644 --- a/packages/frontend/src/pages/channel-editor.vue +++ b/packages/frontend/src/pages/channel-editor.vue @@ -216,7 +216,7 @@ definePageMetadata(() => ({ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - color: var(--navFg); + color: var(--MI_THEME-navFg); } .pinnedNoteRemove { diff --git a/packages/frontend/src/pages/channel.vue b/packages/frontend/src/pages/channel.vue index 790e16e471..9cd2546312 100644 --- a/packages/frontend/src/pages/channel.vue +++ b/packages/frontend/src/pages/channel.vue @@ -300,14 +300,14 @@ definePageMetadata(() => ({ <style lang="scss" module> .main { - min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); + min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px))); } .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - background: var(--acrylicBg); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + border-top: solid 0.5px var(--MI_THEME-divider); } .bannerContainer { @@ -341,7 +341,7 @@ definePageMetadata(() => ({ left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); } .bannerStatus { @@ -352,7 +352,7 @@ definePageMetadata(() => ({ padding: 8px 12px; font-size: 80%; background: rgba(0, 0, 0, 0.7); - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); color: #fff; } @@ -366,8 +366,8 @@ definePageMetadata(() => ({ bottom: 16px; left: 16px; background: rgba(0, 0, 0, 0.7); - color: var(--warn); - border-radius: var(--radius-sm); + color: var(--MI_THEME-warn); + border-radius: var(--MI-radius-sm); font-weight: bold; font-size: 1em; padding: 4px 7px; diff --git a/packages/frontend/src/pages/clip.vue b/packages/frontend/src/pages/clip.vue index 52b852ad17..716cd9a73f 100644 --- a/packages/frontend/src/pages/clip.vue +++ b/packages/frontend/src/pages/clip.vue @@ -33,25 +33,28 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { computed, watch, provide, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { url } from '@@/js/config.js'; +import type { MenuItem } from '@/types/menu.js'; import MkNotes from '@/components/MkNotes.vue'; import { $i } from '@/account.js'; import { i18n } from '@/i18n.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; -import { url } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import { clipsCache } from '@/cache.js'; import { isSupportShare } from '@/scripts/navigator.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { genEmbedCode } from '@/scripts/get-embed-code.js'; -import type { MenuItem } from '@/types/menu.js'; +import { getServerContext } from '@/server-context.js'; + +const CTX_CLIP = getServerContext('clip'); const props = defineProps<{ clipId: string, }>(); -const clip = ref<Misskey.entities.Clip | null>(null); +const clip = ref<Misskey.entities.Clip | null>(CTX_CLIP); const favorited = ref(false); const pagination = { endpoint: 'clips/notes' as const, @@ -64,6 +67,11 @@ const pagination = { const isOwned = computed<boolean | null>(() => $i && clip.value && ($i.id === clip.value.userId)); watch(() => props.clipId, async () => { + if (CTX_CLIP && CTX_CLIP.id === props.clipId) { + clip.value = CTX_CLIP; + return; + } + clip.value = await misskeyApi('clips/show', { clipId: props.clipId, }); @@ -198,7 +206,7 @@ definePageMetadata(() => ({ .user { --height: 32px; padding: 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); line-height: var(--height); } diff --git a/packages/frontend/src/pages/custom-emojis-manager.vue b/packages/frontend/src/pages/custom-emojis-manager.vue index 8904096875..850c1c5eb0 100644 --- a/packages/frontend/src/pages/custom-emojis-manager.vue +++ b/packages/frontend/src/pages/custom-emojis-manager.vue @@ -119,7 +119,7 @@ const selectAll = () => { if (selectedEmojis.value.length > 0) { selectedEmojis.value = []; } else { - selectedEmojis.value = Array.from(emojisPaginationComponent.value.items.values(), item => item.id); + selectedEmojis.value = Array.from(emojisPaginationComponent.value?.items.values(), item => item.id); } }; @@ -136,7 +136,7 @@ const add = async (ev: MouseEvent) => { }, { done: result => { if (result.created) { - emojisPaginationComponent.value.prepend(result.created); + emojisPaginationComponent.value?.prepend(result.created); } }, closed: () => dispose(), @@ -149,12 +149,12 @@ const edit = (emoji) => { }, { done: result => { if (result.updated) { - emojisPaginationComponent.value.updateItem(result.updated.id, (oldEmoji: any) => ({ + emojisPaginationComponent.value?.updateItem(result.updated.id, (oldEmoji) => ({ ...oldEmoji, ...result.updated, })); } else if (result.deleted) { - emojisPaginationComponent.value.removeItem(emoji.id); + emojisPaginationComponent.value?.removeItem(emoji.id); } }, closed: () => dispose(), @@ -239,7 +239,7 @@ const setCategoryBulk = async () => { ids: selectedEmojis.value, category: result, }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const setLicenseBulk = async () => { @@ -251,43 +251,43 @@ const setLicenseBulk = async () => { ids: selectedEmojis.value, license: result, }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const addTagBulk = async () => { const { canceled, result } = await os.inputText({ title: 'Tag', }); - if (canceled) return; + if (canceled || result == null) return; await os.apiWithDialog('admin/emoji/add-aliases-bulk', { ids: selectedEmojis.value, aliases: result.split(' '), }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const removeTagBulk = async () => { const { canceled, result } = await os.inputText({ title: 'Tag', }); - if (canceled) return; + if (canceled || result == null) return; await os.apiWithDialog('admin/emoji/remove-aliases-bulk', { ids: selectedEmojis.value, aliases: result.split(' '), }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const setTagBulk = async () => { const { canceled, result } = await os.inputText({ title: 'Tag', }); - if (canceled) return; + if (canceled || result == null) return; await os.apiWithDialog('admin/emoji/set-aliases-bulk', { ids: selectedEmojis.value, aliases: result.split(' '), }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const delBulk = async () => { @@ -299,7 +299,7 @@ const delBulk = async () => { await os.apiWithDialog('admin/emoji/delete-bulk', { ids: selectedEmojis.value, }); - emojisPaginationComponent.value.reload(); + emojisPaginationComponent.value?.reload(); }; const headerActions = computed(() => [{ @@ -330,28 +330,28 @@ definePageMetadata(() => ({ .ogwlenmc { > .local { .empty { - margin: var(--margin); + margin: var(--MI-margin); } .ldhfsamy { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); grid-gap: 12px; - margin: var(--margin) 0; + margin: var(--MI-margin) 0; > .emoji { display: flex; align-items: center; padding: 11px; text-align: left; - border: solid 1px var(--panel); + border: solid 1px var(--MI_THEME-panel); &:hover { - border-color: var(--inputBorderHover); + border-color: var(--MI_THEME-inputBorderHover); } &.selected { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } > .img { @@ -382,14 +382,14 @@ definePageMetadata(() => ({ > .remote { .empty { - margin: var(--margin); + margin: var(--MI-margin); } .ldhfsamy { display: grid; grid-template-columns: repeat(auto-fill, minmax(190px, 1fr)); grid-gap: 12px; - margin: var(--margin) 0; + margin: var(--MI-margin) 0; > .emoji { display: flex; @@ -398,7 +398,7 @@ definePageMetadata(() => ({ text-align: left; &:hover { - color: var(--accent); + color: var(--MI_THEME-accent); } > .img { diff --git a/packages/frontend/src/pages/drive.file.info.vue b/packages/frontend/src/pages/drive.file.info.vue index 36e95cec2d..9d74a47ec6 100644 --- a/packages/frontend/src/pages/drive.file.info.vue +++ b/packages/frontend/src/pages/drive.file.info.vue @@ -231,8 +231,8 @@ onMounted(async () => { <style lang="scss" module> .filePreviewRoot { - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); // MkMediaList 内ã®ä¸Šéƒ¨ãƒžãƒ¼ã‚¸ãƒ³ 4px padding: calc(1rem - 4px) 1rem 1rem; } @@ -258,12 +258,12 @@ onMounted(async () => { .fileQuickActionsOthersButton { padding: .5rem; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); &:hover, &:focus-visible { - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); text-decoration: none; outline: none; } @@ -285,7 +285,7 @@ onMounted(async () => { align-items: center; min-width: 0; font-weight: 700; - border-radius: var(--radius); + border-radius: var(--MI-radius); font-size: .8rem; >.fileNameEditIcon { @@ -299,12 +299,12 @@ onMounted(async () => { } &:hover { - background-color: var(--accentedBg); + background-color: var(--MI_THEME-accentedBg); >.fileName, >.fileNameEditIcon { visibility: visible; - color: var(--accent); + color: var(--MI_THEME-accent); } } } @@ -322,7 +322,7 @@ onMounted(async () => { display: block; width: 100%; padding: .5rem 1rem; - border-radius: var(--radius); + border-radius: var(--MI-radius); .kvEditIcon { display: inline-block; @@ -332,11 +332,11 @@ onMounted(async () => { } &:hover { - color: var(--accent); - background-color: var(--accentedBg); + color: var(--MI_THEME-accent); + background-color: var(--MI_THEME-accentedBg); .kvEditIcon { - color: var(--accent); + color: var(--MI_THEME-accent); visibility: visible; } } diff --git a/packages/frontend/src/pages/drop-and-fusion.game.vue b/packages/frontend/src/pages/drop-and-fusion.game.vue index 4db952eac2..fb4d599c28 100644 --- a/packages/frontend/src/pages/drop-and-fusion.game.vue +++ b/packages/frontend/src/pages/drop-and-fusion.game.vue @@ -111,7 +111,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="replaying" class="_woodenFrame"> <div class="_woodenFrameInner"> <div style="background: #0004;"> - <div style="height: 10px; background: var(--accent); will-change: width;" :style="{ width: `${(currentFrame / endedAtFrame) * 100}%` }"></div> + <div style="height: 10px; background: var(--MI_THEME-accent); will-change: width;" :style="{ width: `${(currentFrame / endedAtFrame) * 100}%` }"></div> </div> </div> <div class="_woodenFrameInner"> diff --git a/packages/frontend/src/pages/emoji-edit-dialog.vue b/packages/frontend/src/pages/emoji-edit-dialog.vue index c5f0dde878..d3e9ca0dcf 100644 --- a/packages/frontend/src/pages/emoji-edit-dialog.vue +++ b/packages/frontend/src/pages/emoji-edit-dialog.vue @@ -8,9 +8,9 @@ SPDX-License-Identifier: AGPL-3.0-only ref="windowEl" :initialWidth="400" :initialHeight="500" - :canResize="false" - @close="windowEl.close()" - @closed="$emit('closed')" + :canResize="true" + @close="windowEl?.close()" + @closed="emit('closed')" > <template v-if="emoji" #header>:{{ emoji.name }}:</template> <template v-else #header>New emoji</template> @@ -95,14 +95,19 @@ import { selectFile } from '@/scripts/select-file.js'; import MkRolePreview from '@/components/MkRolePreview.vue'; const props = defineProps<{ - emoji?: any, + emoji?: Misskey.entities.EmojiDetailed, +}>(); + +const emit = defineEmits<{ + (ev: 'done', v: { deleted?: boolean; updated?: Misskey.entities.AdminEmojiUpdateRequest; created?: Misskey.entities.AdminEmojiUpdateRequest }): void, + (ev: 'closed'): void }>(); const windowEl = ref<InstanceType<typeof MkWindow> | null>(null); const name = ref<string>(props.emoji ? props.emoji.name : ''); -const category = ref<string>(props.emoji ? props.emoji.category : ''); +const category = ref<string>(props.emoji?.category ? props.emoji.category : ''); const aliases = ref<string>(props.emoji ? props.emoji.aliases.join(' ') : ''); -const license = ref<string>(props.emoji ? (props.emoji.license ?? '') : ''); +const license = ref<string>(props.emoji?.license ? props.emoji.license : ''); const isSensitive = ref(props.emoji ? props.emoji.isSensitive : false); const localOnly = ref(props.emoji ? props.emoji.localOnly : false); const roleIdsThatCanBeUsedThisEmojiAsReaction = ref(props.emoji ? props.emoji.roleIdsThatCanBeUsedThisEmojiAsReaction : []); @@ -115,12 +120,7 @@ watch(roleIdsThatCanBeUsedThisEmojiAsReaction, async () => { const imgUrl = computed(() => file.value ? file.value.url : props.emoji ? `/emoji/${props.emoji.name}.webp` : null); -const emit = defineEmits<{ - (ev: 'done', v: { deleted?: boolean; updated?: any; created?: any }): void, - (ev: 'closed'): void -}>(); - -async function changeImage(ev) { +async function changeImage(ev: Event) { file.value = await selectFile(ev.currentTarget ?? ev.target, null); const candidate = file.value.name.replace(/\.(.+)$/, ''); if (candidate.match(/^[a-z0-9_]+$/)) { @@ -140,7 +140,7 @@ async function addRole() { rolesThatCanBeUsedThisEmojiAsReaction.value.push(role); } -async function removeRole(role, ev) { +async function removeRole(role: Misskey.entities.RoleLite, ev: Event) { rolesThatCanBeUsedThisEmojiAsReaction.value = rolesThatCanBeUsedThisEmojiAsReaction.value.filter(x => x.id !== role.id); } @@ -172,7 +172,7 @@ async function done() { }, }); - windowEl.value.close(); + windowEl.value?.close(); } else { const created = await os.apiWithDialog('admin/emoji/add', params); @@ -180,11 +180,12 @@ async function done() { created: created, }); - windowEl.value.close(); + windowEl.value?.close(); } } async function del() { + if (!props.emoji) return; const { canceled } = await os.confirm({ type: 'warning', text: i18n.tsx.removeAreYouSure({ x: name.value }), @@ -197,7 +198,7 @@ async function del() { emit('done', { deleted: true, }); - windowEl.value.close(); + windowEl.value?.close(); }); } </script> @@ -212,7 +213,7 @@ async function del() { .imgContainer { padding: 8px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .img { @@ -243,9 +244,9 @@ async function del() { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - background: var(--acrylicBg); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-acrylicBg); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/emojis.emoji.vue b/packages/frontend/src/pages/emojis.emoji.vue index 03a3b8f1c0..594a8eda0e 100644 --- a/packages/frontend/src/pages/emojis.emoji.vue +++ b/packages/frontend/src/pages/emojis.emoji.vue @@ -15,18 +15,22 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import * as Misskey from 'misskey-js'; +import { defineAsyncComponent } from 'vue'; +import type { MenuItem } from '@/types/menu.js'; import * as os from '@/os.js'; import { misskeyApiGet } from '@/scripts/misskey-api.js'; import { copyToClipboard } from '@/scripts/copy-to-clipboard.js'; import { i18n } from '@/i18n.js'; import MkCustomEmojiDetailedDialog from '@/components/MkCustomEmojiDetailedDialog.vue'; +import { $i } from '@/account.js'; const props = defineProps<{ emoji: Misskey.entities.EmojiSimple; }>(); function menu(ev) { - os.popupMenu([{ + const menuItems: MenuItem[] = []; + menuItems.push({ type: 'label', text: ':' + props.emoji.name + ':', }, { @@ -48,8 +52,28 @@ function menu(ev) { closed: () => dispose(), }); }, - }], ev.currentTarget ?? ev.target); + }); + + if ($i?.isModerator ?? $i?.isAdmin) { + menuItems.push({ + text: i18n.ts.edit, + icon: 'ti ti-pencil', + action: () => { + edit(props.emoji); + }, + }); + } + + os.popupMenu(menuItems, ev.currentTarget ?? ev.target); } + +const edit = async (emoji) => { + const { dispose } = os.popup(defineAsyncComponent(() => import('@/pages/emoji-edit-dialog.vue')), { + emoji: emoji, + }, { + closed: () => dispose(), + }); +}; </script> <style lang="scss" module> @@ -58,11 +82,11 @@ function menu(ev) { align-items: center; padding: 12px; text-align: left; - background: var(--panel); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-sm); &:hover { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } } diff --git a/packages/frontend/src/pages/explore.featured.vue b/packages/frontend/src/pages/explore.featured.vue index cfdb235d3a..8b16a88ff3 100644 --- a/packages/frontend/src/pages/explore.featured.vue +++ b/packages/frontend/src/pages/explore.featured.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkSpacer :contentMax="800"> - <MkTab v-model="tab" style="margin-bottom: var(--margin);"> + <MkTab v-model="tab" style="margin-bottom: var(--MI-margin);"> <option value="notes">{{ i18n.ts.notes }}</option> <option value="polls">{{ i18n.ts.poll }}</option> </MkTab> diff --git a/packages/frontend/src/pages/explore.users.vue b/packages/frontend/src/pages/explore.users.vue index 805d826946..2550100a42 100644 --- a/packages/frontend/src/pages/explore.users.vue +++ b/packages/frontend/src/pages/explore.users.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkSpacer :contentMax="1200"> - <MkTab v-model="origin" style="margin-bottom: var(--margin);"> + <MkTab v-model="origin" style="margin-bottom: var(--MI-margin);"> <option value="local">{{ i18n.ts.local }}</option> <option value="remote">{{ i18n.ts.remote }}</option> </MkTab> diff --git a/packages/frontend/src/pages/favorites.vue b/packages/frontend/src/pages/favorites.vue index cb1feef016..ab26573b19 100644 --- a/packages/frontend/src/pages/favorites.vue +++ b/packages/frontend/src/pages/favorites.vue @@ -53,7 +53,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .note { - background: var(--panel); - border-radius: var(--radius); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius); } </style> diff --git a/packages/frontend/src/pages/flash/flash-edit.vue b/packages/frontend/src/pages/flash/flash-edit.vue index fd6fadd0b3..d84ec4873b 100644 --- a/packages/frontend/src/pages/flash/flash-edit.vue +++ b/packages/frontend/src/pages/flash/flash-edit.vue @@ -467,8 +467,8 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> .footer { - backdrop-filter: var(--blur, blur(15px)); - background: var(--acrylicBg); - border-top: solid .5px var(--divider); + backdrop-filter: var(--MI-blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + border-top: solid .5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/flash/flash-index.vue b/packages/frontend/src/pages/flash/flash-index.vue index f63a799365..2b85489706 100644 --- a/packages/frontend/src/pages/flash/flash-index.vue +++ b/packages/frontend/src/pages/flash/flash-index.vue @@ -55,7 +55,8 @@ const tab = ref('featured'); const featuredFlashsPagination = { endpoint: 'flash/featured' as const, - noPaging: true, + limit: 5, + offsetMode: true, }; const myFlashsPagination = { endpoint: 'flash/my' as const, diff --git a/packages/frontend/src/pages/flash/flash.vue b/packages/frontend/src/pages/flash/flash.vue index 1229fcfd4e..7a7d52691c 100644 --- a/packages/frontend/src/pages/flash/flash.vue +++ b/packages/frontend/src/pages/flash/flash.vue @@ -51,7 +51,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div><i class="ti ti-clock"></i> {{ i18n.ts.createdAt }}: <MkTime :time="flash.createdAt" mode="detail"/></div> </div> </div> - <MkA v-if="$i && $i.id === flash.userId" :to="`/play/${flash.id}/edit`" style="color: var(--accent);">{{ i18n.ts._play.editThisPage }}</MkA> + <MkA v-if="$i && $i.id === flash.userId" :to="`/play/${flash.id}/edit`" style="color: var(--MI_THEME-accent);">{{ i18n.ts._play.editThisPage }}</MkA> <MkAd :prefer="['horizontal', 'horizontal-big']"/> </div> <MkError v-else-if="error" @retry="fetchFlash()"/> @@ -367,7 +367,7 @@ definePageMetadata(() => ({ justify-content: center; gap: 12px; padding: 16px; - border-bottom: 1px solid var(--divider); + border-bottom: 1px solid var(--MI_THEME-divider); &:last-child { border-bottom: none; diff --git a/packages/frontend/src/pages/follow-requests.vue b/packages/frontend/src/pages/follow-requests.vue index 400dfdbe6d..2f9cacbde9 100644 --- a/packages/frontend/src/pages/follow-requests.vue +++ b/packages/frontend/src/pages/follow-requests.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </template> <template #default="{items}"> - <div class="mk-follow-requests"> + <div class="mk-follow-requests _gaps"> <div v-for="req in items" :key="req.id" class="user _panel"> <MkAvatar class="avatar" :user="displayUser(req)" indicator link preview/> <div class="body"> @@ -29,6 +29,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton class="command" rounded primary @click="accept(displayUser(req))"><i class="ti ti-check"/> {{ i18n.ts.accept }}</MkButton> <MkButton class="command" rounded danger @click="reject(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.reject }}</MkButton> </div> + <div v-else class="commands"> + <MkButton class="command" rounded danger @click="cancel(displayUser(req))"><i class="ti ti-x"/> {{ i18n.ts.cancel }}</MkButton> + </div> </div> </div> </div> @@ -41,38 +44,42 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> +import * as Misskey from 'misskey-js'; import { shallowRef, computed, ref } from 'vue'; -import MkPagination from '@/components/MkPagination.vue'; +import MkPagination, { type Paging } from '@/components/MkPagination.vue'; import MkButton from '@/components/MkButton.vue'; import { userPage, acct } from '@/filters/user.js'; -import { misskeyApi } from '@/scripts/misskey-api.js'; +import * as os from '@/os.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import { infoImageUrl } from '@/instance.js'; +import { $i } from '@/account.js'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; -import { $i } from '@/account'; const paginationComponent = shallowRef<InstanceType<typeof MkPagination>>(); -const pagination = computed(() => tab.value === 'list' - ? { - endpoint: 'following/requests/list' as const, - limit: 10, - } - : { - endpoint: 'following/requests/sent' as const, - limit: 10, - }, -); +const pagination = computed<Paging>(() => tab.value === 'list' ? { + endpoint: 'following/requests/list', + limit: 10, +} : { + endpoint: 'following/requests/sent', + limit: 10, +}); + +function accept(user: Misskey.entities.UserLite) { + os.apiWithDialog('following/requests/accept', { userId: user.id }).then(() => { + paginationComponent.value?.reload(); + }); +} -function accept(user) { - misskeyApi('following/requests/accept', { userId: user.id }).then(() => { +function reject(user: Misskey.entities.UserLite) { + os.apiWithDialog('following/requests/reject', { userId: user.id }).then(() => { paginationComponent.value?.reload(); }); } -function reject(user) { - misskeyApi('following/requests/reject', { userId: user.id }).then(() => { +function cancel(user: Misskey.entities.UserLite) { + os.apiWithDialog('following/requests/cancel', { userId: user.id }).then(() => { paginationComponent.value?.reload(); }); } @@ -86,11 +93,11 @@ const headerActions = computed(() => []); const headerTabs = computed(() => [ { key: 'list', - title: i18n.ts.followRequests, + title: i18n.ts._followRequest.recieved, icon: 'ph-envelope ph-bold ph-lg', }, { key: 'sent', - title: i18n.ts.pendingFollowRequests, + title: i18n.ts._followRequest.sent, icon: 'ph-paper-plane-tilt ph-bold ph-lg', }, ]); @@ -115,7 +122,7 @@ definePageMetadata(() => ({ margin: 0 12px 0 0; width: 42px; height: 42px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } > .body { diff --git a/packages/frontend/src/pages/following-feed.vue b/packages/frontend/src/pages/following-feed.vue index 91f74b2cf9..7fb13c8fcb 100644 --- a/packages/frontend/src/pages/following-feed.vue +++ b/packages/frontend/src/pages/following-feed.vue @@ -127,7 +127,7 @@ definePageMetadata(() => ({ // The universal layout inserts a "spacer" thing that causes a stray scroll bar. // We have to create fake "space" for it to "roll up" and back into the viewport, which removes the scrollbar. - margin-bottom: calc(-1 * var(--minBottomSpacing)); + margin-bottom: calc(-1 * var(--MI-minBottomSpacing)); // Some "just in case" backup properties. // These should not be needed, but help to maintain the layout if the above trick ever stops working. diff --git a/packages/frontend/src/pages/gallery/edit.vue b/packages/frontend/src/pages/gallery/edit.vue index a68a7e5c41..70f8b2c31d 100644 --- a/packages/frontend/src/pages/gallery/edit.vue +++ b/packages/frontend/src/pages/gallery/edit.vue @@ -141,7 +141,7 @@ definePageMetadata(() => ({ top: 8px; left: 9px; padding: 8px; - background: var(--panel); + background: var(--MI_THEME-panel); } > .remove { @@ -149,7 +149,7 @@ definePageMetadata(() => ({ top: 8px; right: 9px; padding: 8px; - background: var(--panel); + background: var(--MI_THEME-panel); } } </style> diff --git a/packages/frontend/src/pages/gallery/index.vue b/packages/frontend/src/pages/gallery/index.vue index e0b137ed28..2162e269bf 100644 --- a/packages/frontend/src/pages/gallery/index.vue +++ b/packages/frontend/src/pages/gallery/index.vue @@ -130,6 +130,6 @@ definePageMetadata(() => ({ display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: 12px; - margin: 0 var(--margin); + margin: 0 var(--MI-margin); } </style> diff --git a/packages/frontend/src/pages/gallery/post.vue b/packages/frontend/src/pages/gallery/post.vue index 6ed119c0c4..539a6a9a7b 100644 --- a/packages/frontend/src/pages/gallery/post.vue +++ b/packages/frontend/src/pages/gallery/post.vue @@ -262,14 +262,14 @@ definePageMetadata(() => ({ align-items: center; margin-top: 16px; padding: 16px 0 0 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); > .like { > .button { - --accent: rgb(241 97 132); - --X8: rgb(241 92 128); - --buttonBg: rgb(216 71 106 / 5%); - --buttonHoverBg: rgb(216 71 106 / 10%); + --MI_THEME-accent: rgb(241 97 132); + --MI_THEME-X8: rgb(241 92 128); + --MI_THEME-buttonBg: rgb(216 71 106 / 5%); + --MI_THEME-buttonHoverBg: rgb(216 71 106 / 10%); color: #ff002f; ::v-deep(.count) { @@ -286,7 +286,7 @@ definePageMetadata(() => ({ margin: 0 8px; &:hover { - color: var(--fgHighlighted); + color: var(--MI_THEME-fgHighlighted); } } } @@ -295,7 +295,7 @@ definePageMetadata(() => ({ > .user { margin-top: 16px; padding: 16px 0 0 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); display: flex; align-items: center; flex-wrap: wrap; @@ -321,7 +321,7 @@ definePageMetadata(() => ({ display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: 12px; - margin: var(--margin); + margin: var(--MI-margin); > .post { diff --git a/packages/frontend/src/pages/games.vue b/packages/frontend/src/pages/games.vue index b52f4decaa..998b8be0f3 100644 --- a/packages/frontend/src/pages/games.vue +++ b/packages/frontend/src/pages/games.vue @@ -35,7 +35,7 @@ definePageMetadata(() => ({ <style module> .link:focus-within { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: -2px; } </style> diff --git a/packages/frontend/src/pages/install-extensions.vue b/packages/frontend/src/pages/install-extensions.vue index 4bee437f65..6d68ed83b4 100644 --- a/packages/frontend/src/pages/install-extensions.vue +++ b/packages/frontend/src/pages/install-extensions.vue @@ -8,76 +8,26 @@ SPDX-License-Identifier: AGPL-3.0-only <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> <MkSpacer :contentMax="500"> <MkLoading v-if="uiPhase === 'fetching'"/> - <div v-else-if="uiPhase === 'confirm' && data" class="_gaps_m" :class="$style.extInstallerRoot"> - <div :class="$style.extInstallerIconWrapper"> - <i v-if="data.type === 'plugin'" class="ti ti-plug"></i> - <i v-else-if="data.type === 'theme'" class="ti ti-palette"></i> - <i v-else class="ti ti-download"></i> - </div> - <h2 :class="$style.extInstallerTitle">{{ i18n.ts._externalResourceInstaller[`_${data.type}`].title }}</h2> - <div :class="$style.extInstallerNormDesc">{{ i18n.ts._externalResourceInstaller.checkVendorBeforeInstall }}</div> - <MkInfo v-if="data.type === 'plugin'" :warn="true">{{ i18n.ts._plugin.installWarn }}</MkInfo> - <FormSection> - <template #label>{{ i18n.ts._externalResourceInstaller[`_${data.type}`].metaTitle }}</template> - <div class="_gaps_s"> - <FormSplit> + <MkExtensionInstaller v-else-if="uiPhase === 'confirm' && data" :extension="data" @confirm="install()"> + <template #additionalInfo> + <FormSection> + <template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template> + <div class="_gaps_s"> <MkKeyValue> - <template #key>{{ i18n.ts.name }}</template> - <template #value>{{ data.meta?.name }}</template> + <template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template> + <template #value><MkUrl :url="url" :showUrlPreview="false"></MkUrl></template> </MkKeyValue> <MkKeyValue> - <template #key>{{ i18n.ts.author }}</template> - <template #value>{{ data.meta?.author }}</template> + <template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template> + <template #value> + <!-- ã“ã®ç”»é¢ãŒå‡ºã¦ã„る時点ã§ãƒãƒƒã‚·ãƒ¥ã®æ¤œè¨¼ã«ã¯æˆåŠŸã—ã¦ã„ã‚‹ --> + <i class="ti ti-check" style="color: var(--MI_THEME-accent)"></i> + </template> </MkKeyValue> - </FormSplit> - <MkKeyValue v-if="data.type === 'plugin'"> - <template #key>{{ i18n.ts.description }}</template> - <template #value>{{ data.meta?.description }}</template> - </MkKeyValue> - <MkKeyValue v-if="data.type === 'plugin'"> - <template #key>{{ i18n.ts.version }}</template> - <template #value>{{ data.meta?.version }}</template> - </MkKeyValue> - <MkKeyValue v-if="data.type === 'plugin'"> - <template #key>{{ i18n.ts.permission }}</template> - <template #value> - <ul :class="$style.extInstallerKVList"> - <li v-for="permission in data.meta?.permissions" :key="permission">{{ i18n.ts._permissions[permission] }}</li> - </ul> - </template> - </MkKeyValue> - <MkKeyValue v-if="data.type === 'theme' && data.meta?.base"> - <template #key>{{ i18n.ts._externalResourceInstaller._meta.base }}</template> - <template #value>{{ i18n.ts[data.meta.base] }}</template> - </MkKeyValue> - <MkFolder> - <template #icon><i class="ti ti-code"></i></template> - <template #label>{{ i18n.ts._plugin.viewSource }}</template> - - <MkCode :code="data.raw ?? ''"/> - </MkFolder> - </div> - </FormSection> - <FormSection> - <template #label>{{ i18n.ts._externalResourceInstaller._vendorInfo.title }}</template> - <div class="_gaps_s"> - <MkKeyValue> - <template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.endpoint }}</template> - <template #value><MkUrl :url="url ?? ''" :showUrlPreview="false"></MkUrl></template> - </MkKeyValue> - <MkKeyValue> - <template #key>{{ i18n.ts._externalResourceInstaller._vendorInfo.hashVerify }}</template> - <template #value> - <!--ã“ã®ç”»é¢ãŒå‡ºã¦ã„る時点ã§ãƒãƒƒã‚·ãƒ¥ã®æ¤œè¨¼ã«ã¯æˆåŠŸã—ã¦ã„ã‚‹--> - <i class="ti ti-check" style="color: var(--accent)"></i> - </template> - </MkKeyValue> - </div> - </FormSection> - <div class="_buttonsCenter"> - <MkButton primary @click="install()"><i class="ti ti-check"></i> {{ i18n.ts.install }}</MkButton> - </div> - </div> + </div> + </FormSection> + </template> + </MkExtensionInstaller> <div v-else-if="uiPhase === 'error'" class="_gaps_m" :class="[$style.extInstallerRoot, $style.error]"> <div :class="$style.extInstallerIconWrapper"> <i class="ti ti-circle-x"></i> @@ -96,14 +46,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed, onActivated, onDeactivated, nextTick } from 'vue'; import MkLoading from '@/components/global/MkLoading.vue'; +import MkExtensionInstaller, { type Extension } from '@/components/MkExtensionInstaller.vue'; import MkButton from '@/components/MkButton.vue'; -import FormSection from '@/components/form/section.vue'; -import FormSplit from '@/components/form/split.vue'; -import MkCode from '@/components/MkCode.vue'; -import MkUrl from '@/components/global/MkUrl.vue'; -import MkInfo from '@/components/MkInfo.vue'; -import MkFolder from '@/components/MkFolder.vue'; import MkKeyValue from '@/components/MkKeyValue.vue'; +import MkUrl from '@/components/global/MkUrl.vue'; +import FormSection from '@/components/form/section.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { AiScriptPluginMeta, parsePluginMeta, installPlugin } from '@/scripts/install-plugin.js'; @@ -124,24 +71,7 @@ const errorKV = ref<{ const url = ref<string | null>(null); const hash = ref<string | null>(null); -const data = ref<{ - type: 'plugin' | 'theme'; - raw: string; - meta?: { - // Plugin & Theme Common - name: string; - author: string; - - // Plugin - description?: string; - version?: string; - permissions?: string[]; - config?: Record<string, any>; - - // Theme - base?: 'light' | 'dark'; - }; -} | null>(null); +const data = ref<Extension | null>(null); function goBack(): void { history.back(); @@ -227,7 +157,7 @@ async function fetch() { data.value = { type: 'theme', meta: { - description, + // description, // 使用ã•れã¦ã„ãªã„ ...meta, }, raw: res.data, @@ -320,8 +250,8 @@ definePageMetadata(() => ({ <style lang="scss" module> .extInstallerRoot { - border-radius: var(--radius); - background: var(--panel); + border-radius: var(--MI-radius); + background: var(--MI_THEME-panel); padding: 1.5rem; } @@ -335,8 +265,8 @@ definePageMetadata(() => ({ margin-left: auto; margin-right: auto; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } .error .extInstallerIconWrapper { @@ -353,9 +283,4 @@ definePageMetadata(() => ({ .extInstallerNormDesc { text-align: center; } - -.extInstallerKVList { - margin-top: 0; - margin-bottom: 0; -} </style> diff --git a/packages/frontend/src/pages/instance-info.vue b/packages/frontend/src/pages/instance-info.vue index a5e6e5ac33..96aebb14d2 100644 --- a/packages/frontend/src/pages/instance-info.vue +++ b/packages/frontend/src/pages/instance-info.vue @@ -59,6 +59,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton @click="refreshMetadata"><i class="ti ti-refresh"></i> Refresh metadata</MkButton> <MkTextarea v-model="moderationNote" manualSave> <template #label>{{ i18n.ts.moderationNote }}</template> + <template #caption>{{ i18n.ts.moderationNoteDescription }}</template> </MkTextarea> </div> </FormSection> @@ -460,7 +461,7 @@ definePageMetadata(() => ({ display: block; margin: 0 16px 0 0; height: 64px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } > .name { diff --git a/packages/frontend/src/pages/invite.vue b/packages/frontend/src/pages/invite.vue index ef485a9446..3416ee6cfc 100644 --- a/packages/frontend/src/pages/invite.vue +++ b/packages/frontend/src/pages/invite.vue @@ -5,10 +5,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkStickyContainer> - <template #header> - <MkPageHeader/> - </template> - <MKSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200"> + <template #header><MkPageHeader/></template> + <MkSpacer v-if="!instance.disableRegistration || !($i && ($i.isAdmin || $i.policies.canInvite))" :contentMax="1200"> <div :class="$style.root"> <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <div :class="$style.text"> @@ -16,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.nothing }} </div> </div> - </MKSpacer> + </MkSpacer> <MkSpacer v-else :contentMax="800"> <div class="_gaps_m" style="text-align: center;"> <div v-if="resetCycle && inviteLimit">{{ i18n.tsx.inviteLimitResetCycle({ time: resetCycle, limit: inviteLimit }) }}</div> @@ -115,6 +113,6 @@ definePageMetadata(() => ({ width: 128px; height: 128px; margin-bottom: 16px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } </style> diff --git a/packages/frontend/src/pages/list.vue b/packages/frontend/src/pages/list.vue index 03ab804d05..e1ba424afc 100644 --- a/packages/frontend/src/pages/list.vue +++ b/packages/frontend/src/pages/list.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkStickyContainer> <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> + <MkSpacer v-if="error != null" :contentMax="1200"> <div :class="$style.root"> <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <p :class="$style.text"> @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ i18n.ts.nothing }} </p> </div> - </MKSpacer> + </MkSpacer> <MkSpacer v-else-if="list" :contentMax="700" :class="$style.main"> <div v-if="list" class="members _margin"> <div :class="$style.member_text">{{ i18n.ts.members }}</div> @@ -50,7 +50,7 @@ const props = defineProps<{ }>(); const list = ref<Misskey.entities.UserList | null>(null); -const error = ref(); +const error = ref<unknown | null>(null); const users = ref<Misskey.entities.UserDetailed[]>([]); function fetchList(): void { @@ -108,7 +108,7 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> .main { - min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); + min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px))); } .userItem { @@ -143,7 +143,7 @@ definePageMetadata(() => ({ width: 128px; height: 128px; margin-bottom: 16px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } .button { diff --git a/packages/frontend/src/pages/lookup.vue b/packages/frontend/src/pages/lookup.vue index 3233953942..6f10c69640 100644 --- a/packages/frontend/src/pages/lookup.vue +++ b/packages/frontend/src/pages/lookup.vue @@ -40,7 +40,7 @@ function fetch() { return; } - let promise: Promise<any>; + let promise: Promise<unknown>; if (uri.startsWith('https://')) { promise = misskeyApi('ap/show', { diff --git a/packages/frontend/src/pages/miauth.vue b/packages/frontend/src/pages/miauth.vue index 7e70f91710..e85d2c29c1 100644 --- a/packages/frontend/src/pages/miauth.vue +++ b/packages/frontend/src/pages/miauth.vue @@ -4,95 +4,79 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkStickyContainer> - <template #header><MkPageHeader :actions="headerActions" :tabs="headerTabs"/></template> - <MkSpacer :contentMax="800"> - <div v-if="$i"> - <div v-if="state == 'waiting'"> - <MkLoading/> - </div> - <div v-if="state == 'denied'"> - <p>{{ i18n.ts._auth.denied }}</p> - </div> - <div v-else-if="state == 'accepted'" class="accepted"> - <p v-if="callback">{{ i18n.ts._auth.callback }}<MkEllipsis/></p> - <p v-else>{{ i18n.ts._auth.pleaseGoBack }}</p> - </div> - <div v-else> - <div v-if="_permissions.length > 0"> - <p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p> - <p v-else>{{ i18n.ts._auth.permissionAsk }}</p> - <ul> - <li v-for="p in _permissions" :key="p">{{ i18n.ts._permissions[p] }}</li> - </ul> - </div> - <div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div> - <div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div> - <div :class="$style.buttons"> - <MkButton inline @click="deny">{{ i18n.ts.cancel }}</MkButton> - <MkButton inline primary @click="accept">{{ i18n.ts.accept }}</MkButton> - </div> - </div> +<div> + <MkAnimBg style="position: fixed; top: 0;"/> + <div :class="$style.formContainer"> + <div :class="$style.form"> + <MkAuthConfirm + ref="authRoot" + :name="name" + :icon="icon || undefined" + :permissions="_permissions" + @accept="onAccept" + @deny="onDeny" + > + <template #consentAdditionalInfo> + <div v-if="callback != null" class="_gaps_s" :class="$style.redirectRoot"> + <div>{{ i18n.ts._auth.byClickingYouWillBeRedirectedToThisUrl }}</div> + <div class="_monospace" :class="$style.redirectUrl">{{ callback }}</div> + </div> + </template> + </MkAuthConfirm> </div> - <div v-else> - <p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p> - <MkSignin @login="onLogin"/> - </div> - </MkSpacer> -</MkStickyContainer> + </div> +</div> </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; -import MkSignin from '@/components/MkSignin.vue'; -import MkButton from '@/components/MkButton.vue'; -import { misskeyApi } from '@/scripts/misskey-api.js'; -import { $i, login } from '@/account.js'; +import { computed, useTemplateRef } from 'vue'; +import * as Misskey from 'misskey-js'; + +import MkAnimBg from '@/components/MkAnimBg.vue'; +import MkAuthConfirm from '@/components/MkAuthConfirm.vue'; + import { i18n } from '@/i18n.js'; +import { misskeyApi } from '@/scripts/misskey-api.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; const props = defineProps<{ session: string; callback?: string; - name: string; - icon: string; - permission: string; // コンマ区切り + name?: string; + icon?: string; + permission?: string; // コンマ区切り }>(); -const _permissions = computed(() => props.permission ? props.permission.split(',') : []); +const _permissions = computed(() => { + return (props.permission ? props.permission.split(',').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) : []); +}); -const state = ref<string | null>(null); +const authRoot = useTemplateRef('authRoot'); -async function accept(): Promise<void> { - state.value = 'waiting'; +async function onAccept(token: string) { await misskeyApi('miauth/gen-token', { session: props.session, name: props.name, iconUrl: props.icon, permission: _permissions.value, + }, token).catch(() => { + authRoot.value?.showUI('failed'); }); - state.value = 'accepted'; - if (props.callback) { + if (props.callback && props.callback !== '') { const cbUrl = new URL(props.callback); - if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:'].includes(cbUrl.protocol)) throw new Error('invalid url'); + if (['javascript:', 'file:', 'data:', 'mailto:', 'tel:', 'vbscript:'].includes(cbUrl.protocol)) throw new Error('invalid url'); cbUrl.searchParams.set('session', props.session); - location.href = cbUrl.href; + location.href = cbUrl.toString(); + } else { + authRoot.value?.showUI('success'); } } -function deny(): void { - state.value = 'denied'; -} - -function onLogin(res): void { - login(res.i); +function onDeny() { + authRoot.value?.showUI('denied'); } -const headerActions = computed(() => []); - -const headerTabs = computed(() => []); - definePageMetadata(() => ({ title: 'MiAuth', icon: 'ti ti-apps', @@ -100,15 +84,38 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> -.buttons { - margin-top: 16px; - display: flex; - gap: 8px; - flex-wrap: wrap; +.formContainer { + min-height: 100svh; + padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px; + box-sizing: border-box; + display: grid; + place-content: center; +} + +.form { + position: relative; + z-index: 10; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + overflow: clip; + max-width: 500px; + width: calc(100vw - 64px); + height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px))); + overflow-y: scroll; +} + +.redirectRoot { + padding: 16px; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-bg); } -.loginMessage { - text-align: center; - margin: 8px 0 24px; +.redirectUrl { + font-size: 90%; + padding: 12px; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); + overflow-x: scroll; } </style> diff --git a/packages/frontend/src/pages/my-antennas/index.vue b/packages/frontend/src/pages/my-antennas/index.vue index 9233695900..540cb34904 100644 --- a/packages/frontend/src/pages/my-antennas/index.vue +++ b/packages/frontend/src/pages/my-antennas/index.vue @@ -73,11 +73,11 @@ onActivated(() => { .antenna { display: block; padding: 16px; - border: solid 1px var(--divider); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); &:hover { - border: solid 1px var(--accent); + border: solid 1px var(--MI_THEME-accent); text-decoration: none; } } diff --git a/packages/frontend/src/pages/my-clips/index.vue b/packages/frontend/src/pages/my-clips/index.vue index ece998a7a5..acf37a9a2f 100644 --- a/packages/frontend/src/pages/my-clips/index.vue +++ b/packages/frontend/src/pages/my-clips/index.vue @@ -77,15 +77,15 @@ async function create() { clipsCache.delete(); - pagingComponent.value.reload(); + pagingComponent.value?.reload(); } function onClipCreated() { - pagingComponent.value.reload(); + pagingComponent.value?.reload(); } function onClipDeleted() { - pagingComponent.value.reload(); + pagingComponent.value?.reload(); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/my-lists/index.vue b/packages/frontend/src/pages/my-lists/index.vue index cd68be4ac3..d08f6fec5a 100644 --- a/packages/frontend/src/pages/my-lists/index.vue +++ b/packages/frontend/src/pages/my-lists/index.vue @@ -85,12 +85,12 @@ onActivated(() => { .list { display: block; padding: 16px; - border: solid 1px var(--divider); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); margin-bottom: 8px; &:hover { - border: solid 1px var(--accent); + border: solid 1px var(--MI_THEME-accent); text-decoration: none; } } diff --git a/packages/frontend/src/pages/my-lists/list.vue b/packages/frontend/src/pages/my-lists/list.vue index 5f195693cc..69e404bd85 100644 --- a/packages/frontend/src/pages/my-lists/list.vue +++ b/packages/frontend/src/pages/my-lists/list.vue @@ -110,7 +110,7 @@ function addUser() { listId: list.value.id, userId: user.id, }).then(() => { - paginationEl.value.reload(); + paginationEl.value?.reload(); }); }); } @@ -126,7 +126,7 @@ async function removeUser(item, ev) { listId: list.value.id, userId: item.userId, }).then(() => { - paginationEl.value.removeItem(item.id); + paginationEl.value?.removeItem(item.id); }); }, }], ev.currentTarget ?? ev.target); @@ -134,7 +134,7 @@ async function removeUser(item, ev) { async function showMembershipMenu(item, ev) { const withRepliesRef = ref(item.withReplies); - + os.popupMenu([{ type: 'switch', text: i18n.ts.showRepliesToOthersInTimeline, @@ -199,7 +199,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .main { - min-height: calc(100cqh - (var(--stickyTop, 0px) + var(--stickyBottom, 0px))); + min-height: calc(100cqh - (var(--MI-stickyTop, 0px) + var(--MI-stickyBottom, 0px))); } .userItem { @@ -234,8 +234,8 @@ definePageMetadata(() => ({ } .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/not-found.vue b/packages/frontend/src/pages/not-found.vue index 93a792c42f..6a2d01b6fa 100644 --- a/packages/frontend/src/pages/not-found.vue +++ b/packages/frontend/src/pages/not-found.vue @@ -24,7 +24,7 @@ const props = defineProps<{ }>(); if (props.showLoginPopup) { - pleaseLogin('/'); + pleaseLogin({ path: '/' }); } const headerActions = computed(() => []); diff --git a/packages/frontend/src/pages/note.vue b/packages/frontend/src/pages/note.vue index d0bbaeddd9..737b0eea4c 100644 --- a/packages/frontend/src/pages/note.vue +++ b/packages/frontend/src/pages/note.vue @@ -60,6 +60,10 @@ import { i18n } from '@/i18n.js'; import { dateString } from '@/filters/date.js'; import MkClipPreview from '@/components/MkClipPreview.vue'; import { defaultStore } from '@/store.js'; +import { pleaseLogin } from '@/scripts/please-login.js'; +import { getServerContext } from '@/server-context.js'; + +const CTX_NOTE = getServerContext('note'); const MkNoteDetailed = defineAsyncComponent(() => (defaultStore.state.noteDesign === 'misskey') ? import('@/components/MkNoteDetailed.vue') : @@ -72,7 +76,7 @@ const props = defineProps<{ initialTab?: string; }>(); -const note = ref<null | Misskey.entities.Note>(); +const note = ref<null | Misskey.entities.Note>(CTX_NOTE); const clips = ref<Misskey.entities.Clip[]>(); const showPrev = ref<'user' | 'channel' | false>(false); const showNext = ref<'user' | 'channel' | false>(false); @@ -121,6 +125,12 @@ function fetchNote() { showPrev.value = false; showNext.value = false; note.value = null; + + if (CTX_NOTE && CTX_NOTE.id === props.noteId) { + note.value = CTX_NOTE; + return; + } + misskeyApi('notes/show', { noteId: props.noteId, }).then(res => { @@ -134,6 +144,11 @@ function fetchNote() { }); } }).catch(err => { + if (err.id === '8e75455b-738c-471d-9f80-62693f33372e') { + pleaseLogin({ + message: i18n.ts.thisContentsAreMarkedAsSigninRequiredByAuthor, + }); + } error.value = err; }); } @@ -182,11 +197,11 @@ definePageMetadata(() => ({ } .loadNext { - margin-bottom: var(--margin); + margin-bottom: var(--MI-margin); } .loadPrev { - margin-top: var(--margin); + margin-top: var(--MI-margin); } .loadButton { @@ -194,7 +209,7 @@ definePageMetadata(() => ({ } .note { - border-radius: var(--radius); - background: var(--panel); + border-radius: var(--MI-radius); + background: var(--MI_THEME-panel); } </style> diff --git a/packages/frontend/src/pages/notifications.vue b/packages/frontend/src/pages/notifications.vue index bd93fc8369..46ee501c76 100644 --- a/packages/frontend/src/pages/notifications.vue +++ b/packages/frontend/src/pages/notifications.vue @@ -102,7 +102,7 @@ definePageMetadata(() => ({ <style module lang="scss"> .notifications { - border-radius: var(--radius); + border-radius: var(--MI-radius); overflow: clip; } </style> diff --git a/packages/frontend/src/pages/oauth.vue b/packages/frontend/src/pages/oauth.vue index 733e34eb2c..8719a769e5 100644 --- a/packages/frontend/src/pages/oauth.vue +++ b/packages/frontend/src/pages/oauth.vue @@ -4,40 +4,28 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<MkStickyContainer> - <template #header><MkPageHeader/></template> - <MkSpacer :contentMax="800"> - <div v-if="$i"> - <div v-if="permissions.length > 0"> - <p v-if="name">{{ i18n.tsx._auth.permission({ name }) }}</p> - <p v-else>{{ i18n.ts._auth.permissionAsk }}</p> - <ul> - <li v-for="p in permissions" :key="p">{{ i18n.ts._permissions[p] }}</li> - </ul> - </div> - <div v-if="name">{{ i18n.tsx._auth.shareAccess({ name }) }}</div> - <div v-else>{{ i18n.ts._auth.shareAccessAsk }}</div> - <form :class="$style.buttons" action="/oauth/decision" accept-charset="utf-8" method="post"> - <input name="login_token" type="hidden" :value="$i.token"/> - <input name="transaction_id" type="hidden" :value="transactionIdMeta?.content"/> - <MkButton inline name="cancel" value="cancel">{{ i18n.ts.cancel }}</MkButton> - <MkButton inline primary>{{ i18n.ts.accept }}</MkButton> - </form> +<div> + <MkAnimBg style="position: fixed; top: 0;"/> + <div :class="$style.formContainer"> + <div :class="$style.form"> + <MkAuthConfirm + ref="authRoot" + :name="name" + :permissions="permissions" + :waitOnDeny="true" + @accept="onAccept" + @deny="onDeny" + /> </div> - <div v-else> - <p :class="$style.loginMessage">{{ i18n.ts._auth.pleaseLogin }}</p> - <MkSignin @login="onLogin"/> - </div> - </MkSpacer> -</MkStickyContainer> + </div> +</div> </template> <script lang="ts" setup> -import MkSignin from '@/components/MkSignin.vue'; -import MkButton from '@/components/MkButton.vue'; -import { $i, login } from '@/account.js'; -import { i18n } from '@/i18n.js'; +import * as Misskey from 'misskey-js'; +import MkAnimBg from '@/components/MkAnimBg.vue'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import MkAuthConfirm from '@/components/MkAuthConfirm.vue'; const transactionIdMeta = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:transaction-id"]'); if (transactionIdMeta) { @@ -45,10 +33,44 @@ if (transactionIdMeta) { } const name = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:client-name"]')?.content; -const permissions = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:scope"]')?.content.split(' ') ?? []; +const permissions = document.querySelector<HTMLMetaElement>('meta[name="misskey:oauth:scope"]')?.content.split(' ').filter((p): p is typeof Misskey.permissions[number] => (Misskey.permissions as readonly string[]).includes(p)) ?? []; + +function doPost(token: string, decision: 'accept' | 'deny') { + const form = document.createElement('form'); + form.action = '/oauth/decision'; + form.method = 'post'; + form.acceptCharset = 'utf-8'; + + const loginToken = document.createElement('input'); + loginToken.type = 'hidden'; + loginToken.name = 'login_token'; + loginToken.value = token; + form.appendChild(loginToken); + + const transactionId = document.createElement('input'); + transactionId.type = 'hidden'; + transactionId.name = 'transaction_id'; + transactionId.value = transactionIdMeta?.content ?? ''; + form.appendChild(transactionId); + + if (decision === 'deny') { + const cancel = document.createElement('input'); + cancel.type = 'hidden'; + cancel.name = 'cancel'; + cancel.value = 'cancel'; + form.appendChild(cancel); + } + + document.body.appendChild(form); + form.submit(); +} + +function onAccept(token: string) { + doPost(token, 'accept'); +} -function onLogin(res): void { - login(res.i); +function onDeny(token: string) { + doPost(token, 'deny'); } definePageMetadata(() => ({ @@ -58,15 +80,24 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> -.buttons { - margin-top: 16px; - display: flex; - gap: 8px; - flex-wrap: wrap; +.formContainer { + min-height: 100svh; + padding: 32px 32px calc(env(safe-area-inset-bottom, 0px) + 32px) 32px; + box-sizing: border-box; + display: grid; + place-content: center; } -.loginMessage { - text-align: center; - margin: 8px 0 24px; +.form { + position: relative; + z-index: 10; + border-radius: var(--MI-radius); + background-color: var(--MI_THEME-panel); + box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); + overflow: clip; + max-width: 500px; + width: calc(100vw - 64px); + height: min(65svh, calc(100svh - calc(env(safe-area-inset-bottom, 0px) + 64px))); + overflow-y: scroll; } </style> diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue index 1cfe7a6d2d..c3ad6657b0 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.image.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <!-- eslint-disable vue/no-mutating-props --> -<XContainer :draggable="true" @remove="() => $emit('remove')"> +<XContainer :draggable="true" @remove="() => emit('remove')"> <template #header><i class="ti ti-photo"></i> {{ i18n.ts._pages.blocks.image }}</template> <template #func> <button @click="choose()"> @@ -30,11 +30,12 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ - modelValue: any + modelValue: Misskey.entities.PageBlock & { type: 'image' }; }>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'image' }): void; + (ev: 'remove'): void; }>(); const file = ref<Misskey.entities.DriveFile | null>(null); diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue index 0a28386986..36e03b4790 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.note.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <!-- eslint-disable vue/no-mutating-props --> -<XContainer :draggable="true" @remove="() => $emit('remove')"> +<XContainer :draggable="true" @remove="() => emit('remove')"> <template #header><i class="ti ti-note"></i> {{ i18n.ts._pages.blocks.note }}</template> <section style="padding: 16px;" class="_gaps_s"> @@ -34,19 +34,24 @@ import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; const props = defineProps<{ - modelValue: any + modelValue: Misskey.entities.PageBlock & { type: 'note' }; }>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'note' }): void; }>(); -const id = ref<any>(props.modelValue.note); +const id = ref(props.modelValue.note); const note = ref<Misskey.entities.Note | null>(null); watch(id, async () => { if (id.value && (id.value.startsWith('http://') || id.value.startsWith('https://'))) { - id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop(); + id.value = (id.value.endsWith('/') ? id.value.slice(0, -1) : id.value).split('/').pop() ?? null; + } + + if (!id.value) { + note.value = null; + return; } emit('update:modelValue', { diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue index 0f8dc33143..3fed07f7e8 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.section.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <!-- eslint-disable vue/no-mutating-props --> -<XContainer :draggable="true" @remove="() => $emit('remove')"> +<XContainer :draggable="true" @remove="() => emit('remove')"> <template #header><i class="ti ti-note"></i> {{ props.modelValue.title }}</template> <template #func> <button class="_button" @click="rename()"> @@ -21,8 +21,9 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -/* eslint-disable vue/no-mutating-props */ + import { defineAsyncComponent, inject, onMounted, watch, ref } from 'vue'; +import * as Misskey from 'misskey-js'; import { v4 as uuid } from 'uuid'; import XContainer from '../page-editor.container.vue'; import * as os from '@/os.js'; @@ -33,14 +34,13 @@ import { getPageBlockList } from '@/pages/page-editor/common.js'; const XBlocks = defineAsyncComponent(() => import('../page-editor.blocks.vue')); -const props = withDefaults(defineProps<{ - modelValue: any, -}>(), { - modelValue: {}, -}); +const props = defineProps<{ + modelValue: Misskey.entities.PageBlock & { type: 'section'; }, +}>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'section' }): void; + (ev: 'remove'): void; }>(); const children = ref(deepClone(props.modelValue.children ?? [])); diff --git a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue index 14c3e6845e..5795b46c00 100644 --- a/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue +++ b/packages/frontend/src/pages/page-editor/els/page-editor.el.text.vue @@ -5,7 +5,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <!-- eslint-disable vue/no-mutating-props --> -<XContainer :draggable="true" @remove="() => $emit('remove')"> +<XContainer :draggable="true" @remove="() => emit('remove')"> <template #header><i class="ti ti-align-left"></i> {{ i18n.ts._pages.blocks.text }}</template> <section> @@ -15,18 +15,19 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -/* eslint-disable vue/no-mutating-props */ + import { watch, ref, shallowRef, onMounted, onUnmounted } from 'vue'; +import * as Misskey from 'misskey-js'; import XContainer from '../page-editor.container.vue'; import { i18n } from '@/i18n.js'; import { Autocomplete } from '@/scripts/autocomplete.js'; const props = defineProps<{ - modelValue: any + modelValue: Misskey.entities.PageBlock & { type: 'text' } }>(); const emit = defineEmits<{ - (ev: 'update:modelValue', value: any): void; + (ev: 'update:modelValue', value: Misskey.entities.PageBlock & { type: 'text' }): void; }>(); let autocomplete: Autocomplete; @@ -63,7 +64,7 @@ onUnmounted(() => { box-shadow: none; padding: 16px; background: transparent; - color: var(--fg); + color: var(--MI_THEME-fg); font-size: 14px; box-sizing: border-box; } diff --git a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue index 4967e73000..f191320180 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.blocks.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.blocks.vue @@ -4,7 +4,7 @@ SPDX-License-Identifier: AGPL-3.0-only --> <template> -<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => $emit('update:modelValue', v)"> +<Sortable :modelValue="modelValue" tag="div" itemKey="id" handle=".drag-handle" :group="{ name: 'blocks' }" :animation="150" :swapThreshold="0.5" @update:modelValue="v => emit('update:modelValue', v)"> <template #item="{element}"> <div :class="$style.item"> <!-- divãŒç„¡ã„ã¨ã‚¨ãƒ©ãƒ¼ã«ãªã‚‹ https://github.com/SortableJS/vue.draggable.next/issues/189 --> diff --git a/packages/frontend/src/pages/page-editor/page-editor.container.vue b/packages/frontend/src/pages/page-editor/page-editor.container.vue index 2531de0e62..1739c2fc00 100644 --- a/packages/frontend/src/pages/page-editor/page-editor.container.vue +++ b/packages/frontend/src/pages/page-editor/page-editor.container.vue @@ -60,12 +60,12 @@ function remove() { .cpjygsrt { position: relative; overflow: hidden; - background: var(--panel); - border: solid 2px var(--X12); - border-radius: var(--radius-sm); + background: var(--MI_THEME-panel); + border: solid 2px var(--MI_THEME-X12); + border-radius: var(--MI-radius-sm); &:hover { - border: solid 2px var(--X13); + border: solid 2px var(--MI_THEME-X13); } &.warn { diff --git a/packages/frontend/src/pages/page.vue b/packages/frontend/src/pages/page.vue index 12b70fa64f..544e112111 100644 --- a/packages/frontend/src/pages/page.vue +++ b/packages/frontend/src/pages/page.vue @@ -357,24 +357,24 @@ definePageMetadata(() => ({ &:hover, &:focus-visible { - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); text-decoration: none; outline: none; } } .pageMain { - border-radius: var(--radius); + border-radius: var(--MI-radius); padding: 2rem; - background: var(--panel); + background: var(--MI_THEME-panel); box-sizing: border-box; } .pageBanner { width: calc(100% + 4rem); margin: -2rem -2rem 1.5rem; - border-radius: var(--radius) var(--radius) 0 0; + border-radius: var(--MI-radius) var(--MI-radius) 0 0; overflow: hidden; position: relative; @@ -399,7 +399,7 @@ definePageMetadata(() => ({ } .pageBannerBgFallback2 { - background-color: var(--accentedBg); + background-color: var(--MI_THEME-accentedBg); } &::after { @@ -409,7 +409,7 @@ definePageMetadata(() => ({ bottom: 0; width: 100%; height: 100px; - background: linear-gradient(0deg, var(--panel), transparent); + background: linear-gradient(0deg, var(--MI_THEME-panel), transparent); } } @@ -433,7 +433,7 @@ definePageMetadata(() => ({ h1 { font-size: 2rem; font-weight: 700; - color: var(--fg); + color: var(--MI_THEME-fg); margin: 0; } @@ -458,7 +458,7 @@ definePageMetadata(() => ({ flex-shrink: 0; display: flex; align-items: center; - gap: var(--marginHalf); + gap: var(--MI-marginHalf); margin-left: auto; } } @@ -472,14 +472,14 @@ definePageMetadata(() => ({ display: flex; align-items: center; - border-top: 1px solid var(--divider); + border-top: 1px solid var(--MI_THEME-divider); padding-top: 1.5rem; margin-bottom: 1.5rem; > .other { margin-left: auto; display: flex; - gap: var(--marginHalf); + gap: var(--MI-marginHalf); } } @@ -487,7 +487,7 @@ definePageMetadata(() => ({ display: flex; align-items: center; - border-top: 1px solid var(--divider); + border-top: 1px solid var(--MI_THEME-divider); padding-top: 1.5rem; margin-bottom: 1.5rem; @@ -526,14 +526,14 @@ definePageMetadata(() => ({ display: flex; align-items: center; flex-wrap: wrap; - gap: var(--marginHalf); + gap: var(--MI-marginHalf); } .relatedPagesRoot { - padding: var(--margin); + padding: var(--MI-margin); } .relatedPagesItem > article { - background-color: var(--panelHighlight) !important; + background-color: var(--MI_THEME-panelHighlight) !important; } </style> diff --git a/packages/frontend/src/pages/registry.keys.vue b/packages/frontend/src/pages/registry.keys.vue index 8dcb8fa477..a13a8f1f4b 100644 --- a/packages/frontend/src/pages/registry.keys.vue +++ b/packages/frontend/src/pages/registry.keys.vue @@ -52,7 +52,7 @@ const props = defineProps<{ const scope = computed(() => props.path ? props.path.split('/') : []); -const keys = ref<any>(null); +const keys = ref<[string, string][]>([]); function fetchKeys() { misskeyApi('i/registry/keys-with-type', { diff --git a/packages/frontend/src/pages/reversi/game.board.vue b/packages/frontend/src/pages/reversi/game.board.vue index 54e66f6e16..429f502133 100644 --- a/packages/frontend/src/pages/reversi/game.board.vue +++ b/packages/frontend/src/pages/reversi/game.board.vue @@ -504,7 +504,7 @@ $gap: 4px; .boardInner { padding: 32px; - background: var(--panel); + background: var(--MI_THEME-panel); box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410; border-radius: 8px; } @@ -574,34 +574,34 @@ $gap: 4px; transition: border 0.25s ease, opacity 0.25s ease; &.boardCell_empty { - border: solid 2px var(--divider); + border: solid 2px var(--MI_THEME-divider); } &.boardCell_empty.boardCell_can { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); opacity: 0.5; } &.boardCell_empty.boardCell_myTurn { - border-color: var(--divider); + border-color: var(--MI_THEME-divider); opacity: 1; &.boardCell_can { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); cursor: pointer; &:hover { - background: var(--accent); + background: var(--MI_THEME-accent); } } } &.boardCell_prev { - box-shadow: 0 0 0 4px var(--accent); + box-shadow: 0 0 0 4px var(--MI_THEME-accent); } &.boardCell_isEnded { - border-color: var(--divider); + border-color: var(--MI_THEME-divider); } &.boardCell_none { diff --git a/packages/frontend/src/pages/reversi/game.setting.vue b/packages/frontend/src/pages/reversi/game.setting.vue index 08bb3cb76c..437a1a2294 100644 --- a/packages/frontend/src/pages/reversi/game.setting.vue +++ b/packages/frontend/src/pages/reversi/game.setting.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template v-else> <div class="_panel"> - <div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--divider);"> + <div style="display: flex; align-items: center; padding: 16px; border-bottom: solid 1px var(--MI_THEME-divider);"> <div>{{ mapName }}</div> <MkButton style="margin-left: auto;" @click="chooseMap">{{ i18n.ts._reversi.chooseBoard }}</MkButton> </div> @@ -87,7 +87,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.footer"> <MkSpacer :contentMax="700" :marginMin="16" :marginMax="16"> <div style="text-align: center;" class="_gaps_s"> - <div v-if="opponentHasSettingsChanged" style="color: var(--warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div> + <div v-if="opponentHasSettingsChanged" style="color: var(--MI_THEME-warn);">{{ i18n.ts._reversi.opponentHasSettingsChanged }}</div> <div> <template v-if="isReady && isOpReady">{{ i18n.ts._reversi.thisGameIsStartedSoon }}<MkEllipsis/></template> <template v-if="isReady && !isOpReady">{{ i18n.ts._reversi.waitingForOther }}<MkEllipsis/></template> @@ -132,7 +132,7 @@ const mapCategories = Array.from(new Set(Object.values(Reversi.maps).map(x => x. const props = defineProps<{ game: Misskey.entities.ReversiGameDetailed; - connection: Misskey.ChannelConnection; + connection: Misskey.ChannelConnection<Misskey.Channels['reversiGame']>; }>(); const shareWhenStart = defineModel<boolean>('shareWhenStart', { default: false }); @@ -217,14 +217,14 @@ function onChangeReadyStates(states) { game.value.user2Ready = states.user2; } -function updateSettings(key: keyof Misskey.entities.ReversiGameDetailed) { +function updateSettings(key: typeof Misskey.reversiUpdateKeys[number]) { props.connection.send('updateSettings', { key: key, value: game.value[key], }); } -function onUpdateSettings({ userId, key, value }: { userId: string; key: keyof Misskey.entities.ReversiGameDetailed; value: any; }) { +function onUpdateSettings<K extends typeof Misskey.reversiUpdateKeys[number]>({ userId, key, value }: { userId: string; key: K; value: Misskey.entities.ReversiGameDetailed[K]; }) { if (userId === $i.id) return; if (game.value[key] === value) return; game.value[key] = value; @@ -273,14 +273,14 @@ onUnmounted(() => { width: 300px; height: 300px; margin: 0 auto; - color: var(--fg); + color: var(--MI_THEME-fg); } .boardCell { display: grid; place-items: center; background: transparent; - border: solid 2px var(--divider); + border: solid 2px var(--MI_THEME-divider); border-radius: 6px; overflow: clip; cursor: pointer; @@ -290,9 +290,9 @@ onUnmounted(() => { } .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - background: var(--acrylicBg); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/reversi/index.vue b/packages/frontend/src/pages/reversi/index.vue index d823861b4a..d608a2411c 100644 --- a/packages/frontend/src/pages/reversi/index.vue +++ b/packages/frontend/src/pages/reversi/index.vue @@ -36,13 +36,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.gamePreviews"> <MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> <div :class="$style.gamePreviewPlayers"> - <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/> <span style="margin: 0 1em;">vs</span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/> <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> </div> <div :class="$style.gamePreviewFooter"> <span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> @@ -63,13 +63,13 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.gamePreviews"> <MkA v-for="g in items" :key="g.id" v-panel :class="[$style.gamePreview, !g.isStarted && !g.isEnded && $style.gamePreviewWaiting, g.isStarted && !g.isEnded && $style.gamePreviewActive]" tabindex="-1" :to="`/reversi/g/${g.id}`"> <div :class="$style.gamePreviewPlayers"> - <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user1Id" style="margin-right: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> <span v-if="g.winnerId === g.user2Id" style="margin-right: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user1"/> <span style="margin: 0 1em;">vs</span> <MkAvatar :class="$style.gamePreviewPlayersAvatar" :user="g.user2"/> <span v-if="g.winnerId === g.user1Id" style="margin-left: 0.75em; visibility: hidden;"><i class="ti ti-x"></i></span> - <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> + <span v-if="g.winnerId === g.user2Id" style="margin-left: 0.75em; color: var(--MI_THEME-accent); font-weight: bold;"><i class="ti ti-trophy"></i></span> </div> <div :class="$style.gamePreviewFooter"> <span v-if="g.isStarted && !g.isEnded" :class="$style.gamePreviewStatusActive">{{ i18n.ts._reversi.playing }}</span> @@ -285,7 +285,7 @@ definePageMetadata(() => ({ .gamePreviews { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); + grid-gap: var(--MI-margin); } .gamePreview { @@ -295,11 +295,11 @@ definePageMetadata(() => ({ } .gamePreviewActive { - box-shadow: inset 0 0 8px 0px var(--accent); + box-shadow: inset 0 0 8px 0px var(--MI_THEME-accent); } .gamePreviewWaiting { - box-shadow: inset 0 0 8px 0px var(--warn); + box-shadow: inset 0 0 8px 0px var(--MI_THEME-warn); } .gamePreviewPlayers { @@ -324,19 +324,19 @@ definePageMetadata(() => ({ .gamePreviewFooter { display: flex; align-items: baseline; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); padding: 6px 10px; font-size: 0.9em; } .gamePreviewStatusActive { - color: var(--accent); + color: var(--MI_THEME-accent); font-weight: bold; animation: blink 2s infinite; } .gamePreviewStatusWaiting { - color: var(--warn); + color: var(--MI_THEME-warn); font-weight: bold; animation: blink 2s infinite; } diff --git a/packages/frontend/src/pages/role.vue b/packages/frontend/src/pages/role.vue index 9ee3fd7f01..d985349bc5 100644 --- a/packages/frontend/src/pages/role.vue +++ b/packages/frontend/src/pages/role.vue @@ -6,7 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <MkStickyContainer> <template #header><MkPageHeader v-model:tab="tab" :displayBackButton="true" :tabs="headerTabs"/></template> - <MKSpacer v-if="!(typeof error === 'undefined')" :contentMax="1200"> + <MkSpacer v-if="error != null" :contentMax="1200"> <div :class="$style.root"> <img :class="$style.img" :src="serverErrorImageUrl" class="_ghost"/> <p :class="$style.text"> @@ -14,7 +14,7 @@ SPDX-License-Identifier: AGPL-3.0-only {{ error }} </p> </div> - </MKSpacer> + </MkSpacer> <MkSpacer v-else-if="tab === 'users'" :contentMax="1200"> <div class="_gaps_s"> <div v-if="role">{{ role.description }}</div> @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> </MkSpacer> <MkSpacer v-else-if="tab === 'timeline'" :contentMax="700"> - <MkTimeline v-if="visible" ref="timeline" src="role" :role="props.role"/> + <MkTimeline v-if="visible" ref="timeline" src="role" :role="props.roleId"/> <div v-else-if="!visible" class="_fullinfo"> <img :src="infoImageUrl" class="_ghost"/> <div>{{ i18n.ts.nothing }}</div> @@ -47,23 +47,24 @@ import { instanceName } from '@@/js/config.js'; import { serverErrorImageUrl, infoImageUrl } from '@/instance.js'; const props = withDefaults(defineProps<{ - role: string; + roleId: string; initialTab?: string; }>(), { initialTab: 'users', }); +// eslint-disable-next-line vue/no-setup-props-reactivity-loss const tab = ref(props.initialTab); -const role = ref<Misskey.entities.Role>(); -const error = ref(); +const role = ref<Misskey.entities.Role | null>(null); +const error = ref<string | null>(null); const visible = ref(false); -watch(() => props.role, () => { +watch(() => props.roleId, () => { misskeyApi('roles/show', { - roleId: props.role, + roleId: props.roleId, }).then(res => { role.value = res; - document.title = `${role.value.name} | ${instanceName}`; + error.value = null; visible.value = res.isExplorable && res.isPublic; }).catch((err) => { if (err.code === 'NO_SUCH_ROLE') { @@ -71,7 +72,6 @@ watch(() => props.role, () => { } else { error.value = i18n.ts.somethingHappened; } - document.title = `${error.value} | ${instanceName}`; }); }, { immediate: true }); @@ -79,7 +79,7 @@ const users = computed(() => ({ endpoint: 'roles/users' as const, limit: 30, params: { - roleId: props.role, + roleId: props.roleId, }, })); @@ -94,7 +94,7 @@ const headerTabs = computed(() => [{ }]); definePageMetadata(() => ({ - title: role.value ? role.value.name : i18n.ts.role, + title: role.value ? role.value.name : (error.value ?? i18n.ts.role), icon: 'ti ti-badge', })); </script> @@ -115,6 +115,6 @@ definePageMetadata(() => ({ width: 128px; height: 128px; margin-bottom: 16px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } </style> diff --git a/packages/frontend/src/pages/scratchpad.vue b/packages/frontend/src/pages/scratchpad.vue index 155d8b82d7..88171f7d70 100644 --- a/packages/frontend/src/pages/scratchpad.vue +++ b/packages/frontend/src/pages/scratchpad.vue @@ -76,7 +76,11 @@ import { claimAchievement } from '@/scripts/achievements.js'; const parser = new Parser(); let aiscript: Interpreter; const code = ref(''); -const logs = ref<any[]>([]); +const logs = ref<{ + id: number; + text: string; + print: boolean; +}[]>([]); const root = ref<AsUiRoot>(); const components = ref<Ref<AsUiComponent>[]>([]); const uiKey = ref(0); @@ -204,7 +208,7 @@ definePageMetadata(() => ({ .root { display: flex; flex-direction: column; - gap: var(--margin); + gap: var(--MI-margin); } .editor { @@ -242,14 +246,14 @@ definePageMetadata(() => ({ } .uiInspectorUnShown { - color: var(--fgTransparent); + color: var(--MI_THEME-fgTransparent); } .uiInspectorType { display: inline-block; border: hidden; border-radius: 10px; - background-color: var(--panelHighlight); + background-color: var(--MI_THEME-panelHighlight); padding: 2px 8px; font-size: 12px; } diff --git a/packages/frontend/src/pages/search.note.vue b/packages/frontend/src/pages/search.note.vue index 66c5c92480..d64537d289 100644 --- a/packages/frontend/src/pages/search.note.vue +++ b/packages/frontend/src/pages/search.note.vue @@ -225,12 +225,12 @@ async function search() { justify-content: center; } .addMeButton { - border: 2px dashed var(--fgTransparent); + border: 2px dashed var(--MI_THEME-fgTransparent); padding: 12px; margin-right: 16px; } .addUserButton { - border: 2px dashed var(--fgTransparent); + border: 2px dashed var(--MI_THEME-fgTransparent); padding: 12px; flex-grow: 1; } diff --git a/packages/frontend/src/pages/settings/2fa.qrdialog.vue b/packages/frontend/src/pages/settings/2fa.qrdialog.vue index 2244047b31..18c82ffdf6 100644 --- a/packages/frontend/src/pages/settings/2fa.qrdialog.vue +++ b/packages/frontend/src/pages/settings/2fa.qrdialog.vue @@ -138,12 +138,13 @@ const token = ref<string | number | null>(null); const backupCodes = ref<string[]>(); function cancel() { - dialog.value.close(); + dialog.value?.close(); } async function tokenDone() { + if (token.value == null) return; const res = await os.apiWithDialog('i/2fa/done', { - token: token.value.toString(), + token: typeof token.value === 'string' ? token.value : token.value.toString(), }); backupCodes.value = res.backupCodes; @@ -166,7 +167,7 @@ function downloadBackupCodes() { } function allDone() { - dialog.value.close(); + dialog.value?.close(); } </script> diff --git a/packages/frontend/src/pages/settings/2fa.vue b/packages/frontend/src/pages/settings/2fa.vue index 6a9a1e16e2..776f59dda3 100644 --- a/packages/frontend/src/pages/settings/2fa.vue +++ b/packages/frontend/src/pages/settings/2fa.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon><i class="ti ti-shield-lock"></i></template> <template #label>{{ i18n.ts.totp }}</template> <template #caption>{{ i18n.ts.totpDescription }}</template> - <template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--success)"></i></template> + <template #suffix><i v-if="$i.twoFactorEnabled" class="ti ti-check" style="color: var(--MI_THEME-success)"></i></template> <div v-if="$i.twoFactorEnabled" class="_gaps_s"> <div v-text="i18n.ts._2fa.alreadyRegistered"/> @@ -84,7 +84,7 @@ import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; import MkLink from '@/components/MkLink.vue'; import * as os from '@/os.js'; -import { signinRequired, updateAccount } from '@/account.js'; +import { signinRequired, updateAccountPartial } from '@/account.js'; import { i18n } from '@/i18n.js'; const $i = signinRequired(); @@ -123,7 +123,7 @@ async function unregisterTOTP(): Promise<void> { password: auth.result.password, token: auth.result.token, }).then(res => { - updateAccount({ + updateAccountPartial({ twoFactorEnabled: false, }); }).catch(error => { diff --git a/packages/frontend/src/pages/settings/accounts.vue b/packages/frontend/src/pages/settings/accounts.vue index 08c9261dcf..97e960675f 100644 --- a/packages/frontend/src/pages/settings/accounts.vue +++ b/packages/frontend/src/pages/settings/accounts.vue @@ -19,13 +19,13 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { defineAsyncComponent, ref, computed } from 'vue'; +import { ref, computed } from 'vue'; import type * as Misskey from 'misskey-js'; import FormSuspense from '@/components/form/suspense.vue'; import MkButton from '@/components/MkButton.vue'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; -import { getAccounts, addAccount as addAccounts, removeAccount as _removeAccount, login, $i } from '@/account.js'; +import { getAccounts, removeAccount as _removeAccount, login, $i, getAccountWithSigninDialog, getAccountWithSignupDialog } from '@/account.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkUserCardMini from '@/components/MkUserCardMini.vue'; @@ -45,7 +45,7 @@ const init = async () => { }); }; -function menu(account, ev) { +function menu(account: Misskey.entities.UserDetailed, ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.switch, icon: 'ti ti-switch-horizontal', @@ -58,7 +58,7 @@ function menu(account, ev) { }], ev.currentTarget ?? ev.target); } -function addAccount(ev) { +function addAccount(ev: MouseEvent) { os.popupMenu([{ text: i18n.ts.existingAccount, action: () => { addExistingAccount(); }, @@ -68,35 +68,31 @@ function addAccount(ev) { }], ev.currentTarget ?? ev.target); } -async function removeAccount(account) { +async function removeAccount(account: Misskey.entities.UserDetailed) { await _removeAccount(account.id); accounts.value = accounts.value.filter(x => x.id !== account.id); } function addExistingAccount() { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), {}, { - done: async res => { - await addAccounts(res.id, res.i); + getAccountWithSigninDialog().then((res) => { + if (res != null) { os.success(); init(); - }, - closed: () => dispose(), + } }); } function createAccount() { - const { dispose } = os.popup(defineAsyncComponent(() => import('@/components/MkSignupDialog.vue')), {}, { - done: async res => { - await addAccounts(res.id, res.i); - switchAccountWithToken(res.i); - }, - closed: () => dispose(), + getAccountWithSignupDialog().then((res) => { + if (res != null) { + switchAccountWithToken(res.token); + } }); } -async function switchAccount(account: any) { - const fetchedAccounts: any[] = await getAccounts(); - const token = fetchedAccounts.find(x => x.id === account.id).token; +async function switchAccount(account: Misskey.entities.UserDetailed) { + const fetchedAccounts = await getAccounts(); + const token = fetchedAccounts.find(x => x.id === account.id)!.token; switchAccountWithToken(token); } diff --git a/packages/frontend/src/pages/settings/apps.vue b/packages/frontend/src/pages/settings/apps.vue index c58ea5d378..6515503505 100644 --- a/packages/frontend/src/pages/settings/apps.vue +++ b/packages/frontend/src/pages/settings/apps.vue @@ -14,30 +14,39 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <template #default="{items}"> <div class="_gaps"> - <div v-for="token in items" :key="token.id" class="_panel" :class="$style.app"> - <img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/> - <div :class="$style.appBody"> - <div :class="$style.appName">{{ token.name }}</div> - <div>{{ token.description }}</div> - <MkKeyValue oneline> - <template #key>{{ i18n.ts.installedDate }}</template> - <template #value><MkTime :time="token.createdAt"/></template> - </MkKeyValue> - <MkKeyValue oneline> - <template #key>{{ i18n.ts.lastUsedDate }}</template> - <template #value><MkTime :time="token.lastUsedAt"/></template> - </MkKeyValue> - <details> - <summary>{{ i18n.ts.details }}</summary> + <MkFolder v-for="token in items" :key="token.id" :defaultOpen="true"> + <template #icon> + <img v-if="token.iconUrl" :class="$style.appIcon" :src="token.iconUrl" alt=""/> + <i v-else class="ti ti-plug"/> + </template> + <template #label>{{ token.name }}</template> + <template #caption>{{ token.description }}</template> + <template #suffix><MkTime :time="token.lastUsedAt"/></template> + <template #footer> + <MkButton danger @click="revoke(token)"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + </template> + + <div class="_gaps_s"> + <div v-if="token.description">{{ token.description }}</div> + <div> + <MkKeyValue oneline> + <template #key>{{ i18n.ts.installedDate }}</template> + <template #value><MkTime :time="token.createdAt" :mode="'detail'"/></template> + </MkKeyValue> + <MkKeyValue oneline> + <template #key>{{ i18n.ts.lastUsedDate }}</template> + <template #value><MkTime :time="token.lastUsedAt" :mode="'detail'"/></template> + </MkKeyValue> + </div> + <MkFolder> + <template #label>{{ i18n.ts.permission }}</template> + <template #suffix>{{ Object.keys(token.permission).length === 0 ? i18n.ts.none : Object.keys(token.permission).length }}</template> <ul> <li v-for="p in token.permission" :key="p">{{ i18n.ts._permissions[p] }}</li> </ul> - </details> - <div> - <MkButton inline danger @click="revoke(token)"><i class="ti ti-trash"></i></MkButton> - </div> + </MkFolder> </div> - </div> + </MkFolder> </div> </template> </FormPagination> @@ -46,12 +55,14 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref, computed } from 'vue'; +import * as Misskey from 'misskey-js'; import FormPagination from '@/components/MkPagination.vue'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { i18n } from '@/i18n.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; import MkKeyValue from '@/components/MkKeyValue.vue'; import MkButton from '@/components/MkButton.vue'; +import MkFolder from '@/components/MkFolder.vue'; import { infoImageUrl } from '@/instance.js'; const list = ref<InstanceType<typeof FormPagination>>(); @@ -67,7 +78,7 @@ const pagination = { function revoke(token) { misskeyApi('i/revoke-token', { tokenId: token.id }).then(() => { - list.value.reload(); + list.value?.reload(); }); } @@ -82,26 +93,9 @@ definePageMetadata(() => ({ </script> <style lang="scss" module> -.app { - display: flex; - padding: 16px; -} - .appIcon { - display: block; - flex-shrink: 0; - margin: 0 12px 0 0; - width: 50px; - height: 50px; - border-radius: var(--radius-sm); -} - -.appBody { - width: calc(100% - 62px); - position: relative; -} - -.appName { - font-weight: bold; + width: 20px; + height: 20px; + border-radius: 4px; } </style> diff --git a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue index 9f7852a71d..c71595380e 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.decoration.vue @@ -10,12 +10,12 @@ SPDX-License-Identifier: AGPL-3.0-only > <div :class="$style.name"><MkCondensedLine :minScale="0.5">{{ decoration.name }}</MkCondensedLine></div> <MkAvatar style="width: 60px; height: 60px;" :user="$i" :decorations="[{ url: decoration.url, angle, flipH, offsetX, offsetY, showBelow }]" forceShowDecoration/> - <i v-if="decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))" :class="$style.lock" class="ti ti-lock"></i> + <i v-if="locked" :class="$style.lock" class="ti ti-lock"></i> </div> </template> <script lang="ts" setup> -import { } from 'vue'; +import { computed } from 'vue'; import { signinRequired } from '@/account.js'; const $i = signinRequired(); @@ -38,14 +38,16 @@ const props = defineProps<{ const emit = defineEmits<{ (ev: 'click'): void; }>(); + +const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))); </script> <style lang="scss" module> .root { cursor: pointer; padding: 16px 16px 28px 16px; - border: solid 2px var(--divider); - border-radius: var(--radius-sm); + border: solid 2px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); text-align: center; font-size: 90%; overflow: clip; @@ -53,8 +55,8 @@ const emit = defineEmits<{ } .active { - background-color: var(--accentedBg); - border-color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + border-color: var(--MI_THEME-accent); } .name { @@ -68,5 +70,6 @@ const emit = defineEmits<{ position: absolute; bottom: 12px; right: 12px; + color: var(--MI_THEME-warn); } </style> diff --git a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue index 4ec4610279..77f9d4af20 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.dialog.vue @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div :class="$style.footer" class="_buttonsCenter"> <MkButton v-if="usingIndex != null" primary rounded @click="update"><i class="ti ti-check"></i> {{ i18n.ts.update }}</MkButton> <MkButton v-if="usingIndex != null" rounded @click="detach"><i class="ti ti-x"></i> {{ i18n.ts.detach }}</MkButton> - <MkButton v-else :disabled="exceeded" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton> + <MkButton v-else :disabled="exceeded || locked" primary rounded @click="attach"><i class="ti ti-check"></i> {{ i18n.ts.attach }}</MkButton> </div> </div> </MkModalWindow> @@ -64,6 +64,7 @@ const props = defineProps<{ id: string; url: string; name: string; + roleIdsThatCanBeUsedThisDecoration: string[]; }; }>(); @@ -88,6 +89,7 @@ const emit = defineEmits<{ const dialog = shallowRef<InstanceType<typeof MkModalWindow>>(); const exceeded = computed(() => ($i.policies.avatarDecorationLimit - $i.avatarDecorations.length) <= 0); +const locked = computed(() => props.decoration.roleIdsThatCanBeUsedThisDecoration.length > 0 && !$i.roles.some(r => props.decoration.roleIdsThatCanBeUsedThisDecoration.includes(r.id))); const angle = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].angle : null) ?? 0); const flipH = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].flipH : null) ?? false); const offsetX = ref((props.usingIndex != null ? $i.avatarDecorations[props.usingIndex].offsetX : null) ?? 0); @@ -115,7 +117,7 @@ const decorationsForPreview = computed(() => { }); function cancel() { - dialog.value.close(); + dialog.value?.close(); } async function update() { @@ -126,7 +128,7 @@ async function update() { offsetY: offsetY.value, showBelow: showBelow.value, }); - dialog.value.close(); + dialog.value?.close(); } async function attach() { @@ -137,12 +139,12 @@ async function attach() { offsetY: offsetY.value, showBelow: showBelow.value, }); - dialog.value.close(); + dialog.value?.close(); } async function detach() { emit('detach'); - dialog.value.close(); + dialog.value?.close(); } </script> @@ -159,8 +161,8 @@ async function detach() { bottom: 0; left: 0; padding: 12px; - border-top: solid 0.5px var(--divider); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + border-top: solid 0.5px var(--MI_THEME-divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } </style> diff --git a/packages/frontend/src/pages/settings/avatar-decoration.vue b/packages/frontend/src/pages/settings/avatar-decoration.vue index 5324a6b7f7..efcd2a5f3f 100644 --- a/packages/frontend/src/pages/settings/avatar-decoration.vue +++ b/packages/frontend/src/pages/settings/avatar-decoration.vue @@ -148,7 +148,7 @@ definePageMetadata(() => ({ .current { padding: 16px; - border-radius: var(--radius); + border-radius: var(--MI-radius); } .decorations { diff --git a/packages/frontend/src/pages/settings/drive-cleaner.vue b/packages/frontend/src/pages/settings/drive-cleaner.vue index 432295aa7f..1ad3613e4b 100644 --- a/packages/frontend/src/pages/settings/drive-cleaner.vue +++ b/packages/frontend/src/pages/settings/drive-cleaner.vue @@ -177,7 +177,7 @@ definePageMetadata(() => ({ align-items: center; &:hover { - color: var(--accent); + color: var(--MI_THEME-accent); } } @@ -197,7 +197,7 @@ definePageMetadata(() => ({ height: 12px; background: rgba(0, 0, 0, 0.1); overflow: clip; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } .meterValue { diff --git a/packages/frontend/src/pages/settings/drive.vue b/packages/frontend/src/pages/settings/drive.vue index c1e3258631..eef2e5b505 100644 --- a/packages/frontend/src/pages/settings/drive.vue +++ b/packages/frontend/src/pages/settings/drive.vue @@ -152,12 +152,12 @@ definePageMetadata(() => ({ .meter { height: 10px; background: rgba(0, 0, 0, 0.1); - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); overflow: clip; } .meterValue { height: 100%; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } </style> diff --git a/packages/frontend/src/pages/settings/email.vue b/packages/frontend/src/pages/settings/email.vue index f226647569..d452f249b6 100644 --- a/packages/frontend/src/pages/settings/email.vue +++ b/packages/frontend/src/pages/settings/email.vue @@ -10,7 +10,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkInput v-model="emailAddress" type="email" manualSave> <template #prefix><i class="ti ti-mail"></i></template> <template v-if="$i.email && !$i.emailVerified" #caption>{{ i18n.ts.verificationEmailSent }}</template> - <template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--success);"></i> {{ i18n.ts.emailVerified }}</template> + <template v-else-if="emailAddress === $i.email && $i.emailVerified" #caption><i class="ti ti-check" style="color: var(--MI_THEME-success);"></i> {{ i18n.ts.emailVerified }}</template> </MkInput> </FormSection> diff --git a/packages/frontend/src/pages/settings/emoji-picker.vue b/packages/frontend/src/pages/settings/emoji-picker.vue index bb919c6c53..7ddc1eab71 100644 --- a/packages/frontend/src/pages/settings/emoji-picker.vue +++ b/packages/frontend/src/pages/settings/emoji-picker.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps"> <div> - <div v-panel style="border-radius: var(--radius-sm);"> + <div v-panel style="border-radius: var(--MI-radius-sm);"> <Sortable v-model="pinnedEmojisForReaction" :class="$style.emojis" @@ -52,7 +52,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div class="_gaps"> <div> - <div v-panel style="border-radius: var(--radius-sm);"> + <div v-panel style="border-radius: var(--MI-radius-sm);"> <Sortable v-model="pinnedEmojis" :class="$style.emojis" @@ -287,9 +287,9 @@ definePageMetadata(() => ({ <style lang="scss" module> .tab { - margin: calc(var(--margin) / 2) 0; - padding: calc(var(--margin) / 2) 0; - background: var(--bg); + margin: calc(var(--MI-margin) / 2) 0; + padding: calc(var(--MI-margin) / 2) 0; + background: var(--MI_THEME-bg); } .emojis { @@ -311,6 +311,6 @@ definePageMetadata(() => ({ .editorCaption { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/pages/settings/index.vue b/packages/frontend/src/pages/settings/index.vue index f6f2ffc553..552b4ee028 100644 --- a/packages/frontend/src/pages/settings/index.vue +++ b/packages/frontend/src/pages/settings/index.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div v-if="!(narrow && currentPage?.route.name == null)" class="main"> <div class="bkzroven" style="container-type: inline-size;"> - <RouterView/> + <RouterView nested/> </div> </div> </div> diff --git a/packages/frontend/src/pages/settings/mute-block.vue b/packages/frontend/src/pages/settings/mute-block.vue index a5c381200f..82aeb6063f 100644 --- a/packages/frontend/src/pages/settings/mute-block.vue +++ b/packages/frontend/src/pages/settings/mute-block.vue @@ -243,7 +243,7 @@ definePageMetadata(() => ({ .userItemSub { padding: 6px 12px; font-size: 85%; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } .userItemMainBody { diff --git a/packages/frontend/src/pages/settings/navbar.vue b/packages/frontend/src/pages/settings/navbar.vue index a0e6cad9c8..c38cdc4fc2 100644 --- a/packages/frontend/src/pages/settings/navbar.vue +++ b/packages/frontend/src/pages/settings/navbar.vue @@ -100,10 +100,6 @@ function reset() { })); } -watch(menuDisplay, async () => { - await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); -}); - const headerActions = computed(() => []); const headerTabs = computed(() => []); @@ -122,7 +118,7 @@ definePageMetadata(() => ({ text-overflow: ellipsis; overflow: hidden; white-space: nowrap; - color: var(--navFg); + color: var(--MI_THEME-navFg); } .itemIcon { diff --git a/packages/frontend/src/pages/settings/notifications.notification-config.vue b/packages/frontend/src/pages/settings/notifications.notification-config.vue index 1a945aa3ad..0ea415f673 100644 --- a/packages/frontend/src/pages/settings/notifications.notification-config.vue +++ b/packages/frontend/src/pages/settings/notifications.notification-config.vue @@ -6,13 +6,7 @@ SPDX-License-Identifier: AGPL-3.0-only <template> <div class="_gaps_m"> <MkSelect v-model="type"> - <option value="all">{{ i18n.ts.all }}</option> - <option v-if="hasSender" value="following">{{ i18n.ts.following }}</option> - <option v-if="hasSender" value="follower">{{ i18n.ts.followers }}</option> - <option v-if="hasSender" value="mutualFollow">{{ i18n.ts.mutualFollow }}</option> - <option v-if="hasSender" value="followingOrFollower">{{ i18n.ts.followingOrFollower }}</option> - <option v-if="hasSender" value="list">{{ i18n.ts.userList }}</option> - <option value="never">{{ i18n.ts.none }}</option> + <option v-for="type in props.configurableTypes ?? notificationConfigTypes" :key="type" :value="type">{{ notificationConfigTypesI18nMap[type] }}</option> </MkSelect> <MkSelect v-if="type === 'list'" v-model="userListId"> @@ -21,34 +15,61 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSelect> <div class="_buttons"> - <MkButton inline primary @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton inline primary :disabled="type === 'list' && userListId === null" @click="save"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> </div> </div> </template> +<script lang="ts"> +const notificationConfigTypes = [ + 'all', + 'following', + 'follower', + 'mutualFollow', + 'followingOrFollower', + 'list', + 'never' +] as const; + +export type NotificationConfig = { + type: Exclude<typeof notificationConfigTypes[number], 'list'>; +} | { + type: 'list'; + userListId: string; +}; +</script> + <script lang="ts" setup> -import { ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { ref } from 'vue'; import MkSelect from '@/components/MkSelect.vue'; import MkButton from '@/components/MkButton.vue'; import { i18n } from '@/i18n.js'; -const props = withDefaults(defineProps<{ - value: any; +const props = defineProps<{ + value: NotificationConfig; userLists: Misskey.entities.UserList[]; - hasSender: boolean; -}>(), { - hasSender: true, -}); + configurableTypes?: NotificationConfig['type'][]; // If not specified, all types are configurable +}>(); const emit = defineEmits<{ - (ev: 'update', result: any): void; + (ev: 'update', result: NotificationConfig): void; }>(); +const notificationConfigTypesI18nMap: Record<typeof notificationConfigTypes[number], string> = { + all: i18n.ts.all, + following: i18n.ts.following, + follower: i18n.ts.followers, + mutualFollow: i18n.ts.mutualFollow, + followingOrFollower: i18n.ts.followingOrFollower, + list: i18n.ts.userList, + never: i18n.ts.none, +}; + const type = ref(props.value.type); -const userListId = ref(props.value.userListId); +const userListId = ref(props.value.type === 'list' ? props.value.userListId : null); function save() { - emit('update', { type: type.value, userListId: userListId.value }); + emit('update', type.value === 'list' ? { type: type.value, userListId: userListId.value! } : { type: type.value }); } </script> diff --git a/packages/frontend/src/pages/settings/notifications.vue b/packages/frontend/src/pages/settings/notifications.vue index 491102fece..8ffe0d6a7a 100644 --- a/packages/frontend/src/pages/settings/notifications.vue +++ b/packages/frontend/src/pages/settings/notifications.vue @@ -22,7 +22,12 @@ SPDX-License-Identifier: AGPL-3.0-only }} </template> - <XNotificationConfig :userLists="userLists" :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" :hasSender="!(notificationTypesWithoutSender.includes(type))" @update="(res) => updateReceiveConfig(type, res)"/> + <XNotificationConfig + :userLists="userLists" + :value="$i.notificationRecieveConfig[type] ?? { type: 'all' }" + :configurableTypes="onlyOnOrOffNotificationTypes.includes(type) ? ['all', 'never'] : undefined" + @update="(res) => updateReceiveConfig(type, res)" + /> </MkFolder> </div> </FormSection> @@ -58,7 +63,7 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { shallowRef, computed } from 'vue'; -import XNotificationConfig from './notifications.notification-config.vue'; +import XNotificationConfig, { type NotificationConfig } from './notifications.notification-config.vue'; import FormLink from '@/components/form/link.vue'; import FormSection from '@/components/form/section.vue'; import MkFolder from '@/components/MkFolder.vue'; @@ -73,8 +78,9 @@ import { notificationTypes } from '@@/js/const.js'; const $i = signinRequired(); -const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] as const satisfies (typeof notificationTypes[number])[]; -const notificationTypesWithoutSender = ['achievementEarned'] as const satisfies (typeof notificationTypes[number])[]; +const nonConfigurableNotificationTypes = ['note', 'roleAssigned', 'followRequestAccepted', 'test', 'exportCompleted'] satisfies (typeof notificationTypes[number])[] as string[]; + +const onlyOnOrOffNotificationTypes = ['app', 'achievementEarned', 'login'] satisfies (typeof notificationTypes[number])[] as string[]; const allowButton = shallowRef<InstanceType<typeof MkPushNotificationAllowButton>>(); const pushRegistrationInServer = computed(() => allowButton.value?.pushRegistrationInServer); @@ -89,7 +95,7 @@ async function readAllNotifications() { await os.apiWithDialog('notifications/mark-all-as-read'); } -async function updateReceiveConfig(type, value) { +async function updateReceiveConfig(type: typeof notificationTypes[number], value: NotificationConfig) { await os.apiWithDialog('i/update', { notificationRecieveConfig: { ...$i.notificationRecieveConfig, diff --git a/packages/frontend/src/pages/settings/other.vue b/packages/frontend/src/pages/settings/other.vue index eb9d158c1e..abe4524126 100644 --- a/packages/frontend/src/pages/settings/other.vue +++ b/packages/frontend/src/pages/settings/other.vue @@ -65,6 +65,9 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="enableCondensedLine"> <template #label>Enable condensed line</template> </MkSwitch> + <MkSwitch v-model="skipNoteRender"> + <template #label>Enable note render skipping</template> + </MkSwitch> </div> </MkFolder> @@ -116,9 +119,14 @@ const $i = signinRequired(); const reportError = computed(defaultStore.makeGetterSetter('reportError')); const enableCondensedLine = computed(defaultStore.makeGetterSetter('enableCondensedLine')); +const skipNoteRender = computed(defaultStore.makeGetterSetter('skipNoteRender')); const devMode = computed(defaultStore.makeGetterSetter('devMode')); const defaultWithReplies = computed(defaultStore.makeGetterSetter('defaultWithReplies')); +watch(skipNoteRender, async () => { + await reloadAsk({ reason: i18n.ts.reloadToApplySetting, unison: true }); +}); + async function deleteAccount() { { const { canceled } = await os.confirm({ diff --git a/packages/frontend/src/pages/settings/preferences-backups.vue b/packages/frontend/src/pages/settings/preferences-backups.vue index 8036ef5555..f7dcc7b139 100644 --- a/packages/frontend/src/pages/settings/preferences-backups.vue +++ b/packages/frontend/src/pages/settings/preferences-backups.vue @@ -469,7 +469,7 @@ definePageMetadata(() => ({ <style lang="scss" module> .buttons { display: flex; - gap: var(--margin); + gap: var(--MI-margin); flex-wrap: wrap; } diff --git a/packages/frontend/src/pages/settings/privacy.vue b/packages/frontend/src/pages/settings/privacy.vue index dccfb584ae..790f9e44e2 100644 --- a/packages/frontend/src/pages/settings/privacy.vue +++ b/packages/frontend/src/pages/settings/privacy.vue @@ -49,6 +49,93 @@ SPDX-License-Identifier: AGPL-3.0-only </MkSwitch> <FormSection> + <template #label>{{ i18n.ts.lockdown }}<span class="_beta">{{ i18n.ts.beta }}</span></template> + + <div class="_gaps_m"> + <MkSwitch :modelValue="requireSigninToViewContents" @update:modelValue="update_requireSigninToViewContents"> + {{ i18n.ts._accountSettings.requireSigninToViewContents }} + <template #caption> + <div>{{ i18n.ts._accountSettings.requireSigninToViewContentsDescription1 }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription2 }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.requireSigninToViewContentsDescription3 }}</div> + </template> + </MkSwitch> + + <FormSlot> + <template #label>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBefore }}</template> + + <div class="_gaps_s"> + <MkSelect :modelValue="makeNotesFollowersOnlyBefore_type" @update:modelValue="makeNotesFollowersOnlyBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null"> + <option :value="null">{{ i18n.ts.none }}</option> + <option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option> + <option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option> + </MkSelect> + + <MkSelect v-if="makeNotesFollowersOnlyBefore_type === 'relative'" v-model="makeNotesFollowersOnlyBefore"> + <option :value="-3600">{{ i18n.ts.oneHour }}</option> + <option :value="-86400">{{ i18n.ts.oneDay }}</option> + <option :value="-259200">{{ i18n.ts.threeDays }}</option> + <option :value="-604800">{{ i18n.ts.oneWeek }}</option> + <option :value="-2592000">{{ i18n.ts.oneMonth }}</option> + <option :value="-7776000">{{ i18n.ts.threeMonths }}</option> + <option :value="-31104000">{{ i18n.ts.oneYear }}</option> + </MkSelect> + + <MkInput + v-if="makeNotesFollowersOnlyBefore_type === 'absolute'" + :modelValue="formatDateTimeString(new Date(makeNotesFollowersOnlyBefore * 1000), 'yyyy-MM-dd')" + type="date" + :manualSave="true" + @update:modelValue="makeNotesFollowersOnlyBefore = Math.floor(new Date($event).getTime() / 1000)" + > + </MkInput> + </div> + + <template #caption> + <div>{{ i18n.ts._accountSettings.makeNotesFollowersOnlyBeforeDescription }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div> + </template> + </FormSlot> + + <FormSlot> + <template #label>{{ i18n.ts._accountSettings.makeNotesHiddenBefore }}</template> + + <div class="_gaps_s"> + <MkSelect :modelValue="makeNotesHiddenBefore_type" @update:modelValue="makeNotesHiddenBefore = $event === 'relative' ? -604800 : $event === 'absolute' ? Math.floor(Date.now() / 1000) : null"> + <option :value="null">{{ i18n.ts.none }}</option> + <option value="relative">{{ i18n.ts._accountSettings.notesHavePassedSpecifiedPeriod }}</option> + <option value="absolute">{{ i18n.ts._accountSettings.notesOlderThanSpecifiedDateAndTime }}</option> + </MkSelect> + + <MkSelect v-if="makeNotesHiddenBefore_type === 'relative'" v-model="makeNotesHiddenBefore"> + <option :value="-3600">{{ i18n.ts.oneHour }}</option> + <option :value="-86400">{{ i18n.ts.oneDay }}</option> + <option :value="-259200">{{ i18n.ts.threeDays }}</option> + <option :value="-604800">{{ i18n.ts.oneWeek }}</option> + <option :value="-2592000">{{ i18n.ts.oneMonth }}</option> + <option :value="-7776000">{{ i18n.ts.threeMonths }}</option> + <option :value="-31104000">{{ i18n.ts.oneYear }}</option> + </MkSelect> + + <MkInput + v-if="makeNotesHiddenBefore_type === 'absolute'" + :modelValue="formatDateTimeString(new Date(makeNotesHiddenBefore * 1000), 'yyyy-MM-dd')" + type="date" + :manualSave="true" + @update:modelValue="makeNotesHiddenBefore = Math.floor(new Date($event).getTime() / 1000)" + > + </MkInput> + </div> + + <template #caption> + <div>{{ i18n.ts._accountSettings.makeNotesHiddenBeforeDescription }}</div> + <div><i class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> {{ i18n.ts._accountSettings.mayNotEffectForFederatedNotes }}</div> + </template> + </FormSlot> + </div> + </FormSection> + + <FormSection> <div class="_gaps_m"> <MkSwitch v-model="rememberNoteVisibility" @update:modelValue="save()">{{ i18n.ts.rememberNoteVisibility }}</MkSwitch> <MkFolder v-if="!rememberNoteVisibility"> @@ -76,7 +163,7 @@ SPDX-License-Identifier: AGPL-3.0-only </template> <script lang="ts" setup> -import { ref, computed } from 'vue'; +import { ref, computed, watch } from 'vue'; import MkSwitch from '@/components/MkSwitch.vue'; import MkSelect from '@/components/MkSelect.vue'; import FormSection from '@/components/form/section.vue'; @@ -86,6 +173,10 @@ import { defaultStore } from '@/store.js'; import { i18n } from '@/i18n.js'; import { signinRequired } from '@/account.js'; import { definePageMetadata } from '@/scripts/page-metadata.js'; +import FormSlot from '@/components/form/slot.vue'; +import { formatDateTimeString } from '@/scripts/format-time-string.js'; +import MkInput from '@/components/MkInput.vue'; +import * as os from '@/os.js'; const $i = signinRequired(); @@ -95,6 +186,9 @@ const noCrawle = ref($i.noCrawle); const noindex = ref($i.noindex); const enableRss = ref($i.enableRss); const isExplorable = ref($i.isExplorable); +const requireSigninToViewContents = ref($i.requireSigninToViewContents ?? false); +const makeNotesFollowersOnlyBefore = ref($i.makeNotesFollowersOnlyBefore ?? null); +const makeNotesHiddenBefore = ref($i.makeNotesHiddenBefore ?? null); const hideOnlineStatus = ref($i.hideOnlineStatus); const publicReactions = ref($i.publicReactions); const followingVisibility = ref($i.followingVisibility); @@ -105,6 +199,43 @@ const defaultNoteLocalOnly = computed(defaultStore.makeGetterSetter('defaultNote const rememberNoteVisibility = computed(defaultStore.makeGetterSetter('rememberNoteVisibility')); const keepCw = computed(defaultStore.makeGetterSetter('keepCw')); +const makeNotesFollowersOnlyBefore_type = computed(() => { + if (makeNotesFollowersOnlyBefore.value == null) { + return null; + } else if (makeNotesFollowersOnlyBefore.value >= 0) { + return 'absolute'; + } else { + return 'relative'; + } +}); + +const makeNotesHiddenBefore_type = computed(() => { + if (makeNotesHiddenBefore.value == null) { + return null; + } else if (makeNotesHiddenBefore.value >= 0) { + return 'absolute'; + } else { + return 'relative'; + } +}); + +watch([makeNotesFollowersOnlyBefore, makeNotesHiddenBefore], () => { + save(); +}); + +async function update_requireSigninToViewContents(value: boolean) { + if (value) { + const { canceled } = await os.confirm({ + type: 'warning', + text: i18n.ts.acknowledgeNotesAndEnable, + }); + if (canceled) return; + } + + requireSigninToViewContents.value = value; + save(); +} + function save() { misskeyApi('i/update', { isLocked: !!isLocked.value, @@ -113,6 +244,9 @@ function save() { noindex: !!noindex.value, enableRss: !!enableRss.value, isExplorable: !!isExplorable.value, + requireSigninToViewContents: !!requireSigninToViewContents.value, + makeNotesFollowersOnlyBefore: makeNotesFollowersOnlyBefore.value, + makeNotesHiddenBefore: makeNotesHiddenBefore.value, hideOnlineStatus: !!hideOnlineStatus.value, publicReactions: !!publicReactions.value, followingVisibility: followingVisibility.value, diff --git a/packages/frontend/src/pages/settings/profile.vue b/packages/frontend/src/pages/settings/profile.vue index c94cd512f3..9ff4a63e09 100644 --- a/packages/frontend/src/pages/settings/profile.vue +++ b/packages/frontend/src/pages/settings/profile.vue @@ -52,14 +52,17 @@ SPDX-License-Identifier: AGPL-3.0-only <MkFolder> <template #icon><i class="ti ti-list"></i></template> <template #label>{{ i18n.ts._profile.metadataEdit }}</template> - - <div :class="$style.metadataRoot"> - <div :class="$style.metadataMargin"> - <MkButton :disabled="fields.length >= 16" inline style="margin-right: 8px;" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> - <MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" inline danger style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> - <MkButton v-else inline style="margin-right: 8px;" @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton> - <MkButton inline primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <template #footer> + <div class="_buttons"> + <MkButton primary @click="saveFields"><i class="ti ti-check"></i> {{ i18n.ts.save }}</MkButton> + <MkButton :disabled="fields.length >= 16" @click="addField"><i class="ti ti-plus"></i> {{ i18n.ts.add }}</MkButton> + <MkButton v-if="!fieldEditMode" :disabled="fields.length <= 1" danger @click="fieldEditMode = !fieldEditMode"><i class="ti ti-trash"></i> {{ i18n.ts.delete }}</MkButton> + <MkButton v-else @click="fieldEditMode = !fieldEditMode"><i class="ti ti-arrows-sort"></i> {{ i18n.ts.rearrange }}</MkButton> </div> + </template> + + <div :class="$style.metadataRoot" class="_gaps_s"> + <MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo> <Sortable v-model="fields" @@ -71,24 +74,20 @@ SPDX-License-Identifier: AGPL-3.0-only @end="e => e.item.classList.remove('active')" > <template #item="{element, index}"> - <div :class="$style.fieldDragItem"> + <div v-panel :class="$style.fieldDragItem"> <button v-if="!fieldEditMode" class="_button" :class="$style.dragItemHandle" tabindex="-1"><i class="ti ti-menu"></i></button> <button v-if="fieldEditMode" :disabled="fields.length <= 1" class="_button" :class="$style.dragItemRemove" @click="deleteField(index)"><i class="ti ti-x"></i></button> <div :class="$style.dragItemForm"> <FormSplit :minWidth="200"> - <MkInput v-model="element.name" small> - <template #label>{{ i18n.ts._profile.metadataLabel }}</template> + <MkInput v-model="element.name" small :placeholder="i18n.ts._profile.metadataLabel"> </MkInput> - <MkInput v-model="element.value" small> - <template #label>{{ i18n.ts._profile.metadataContent }}</template> + <MkInput v-model="element.value" small :placeholder="i18n.ts._profile.metadataContent"> </MkInput> </FormSplit> </div> </div> </template> </Sortable> - - <MkInfo>{{ i18n.ts._profile.verifiedLinkDescription }}</MkInfo> </div> </MkFolder> <template #caption>{{ i18n.ts._profile.metadataDescription }}</template> @@ -158,6 +157,10 @@ const setMaxBirthDate = () => { return `${y}-12-31`; }; +function assertVaildLang(lang: string | null): lang is keyof typeof langmap { + return lang != null && lang in langmap; +} + const profile = reactive({ name: $i.name, description: $i.description, @@ -165,7 +168,7 @@ const profile = reactive({ location: $i.location, birthday: $i.birthday, listenbrainz: $i.listenbrainz, - lang: $i.lang, + lang: assertVaildLang($i.lang) ? $i.lang : null, isBot: $i.isBot ?? false, isCat: $i.isCat ?? false, speakAsCat: $i.speakAsCat ?? false, @@ -229,6 +232,11 @@ function save() { isBot: !!profile.isBot, isCat: !!profile.isCat, speakAsCat: !!profile.speakAsCat, + }, undefined, { + '0b3f9f6a-2f4d-4b1f-9fb4-49d3a2fd7191': { + title: i18n.ts.yourNameContainsProhibitedWords, + text: i18n.ts.yourNameContainsProhibitedWordsDescription, + }, }); globalEvents.emit('requestClearPageCache'); claimAchievement('profileFilled'); @@ -417,7 +425,7 @@ definePageMetadata(() => ({ height: 130px; background-size: cover; background-position: center; - border-bottom: solid 1px var(--divider); + border-bottom: solid 1px var(--MI_THEME-divider); overflow: clip; } @@ -449,19 +457,11 @@ definePageMetadata(() => ({ container-type: inline-size; } -.metadataMargin { - margin-bottom: 1.5em; -} - .fieldDragItem { display: flex; - padding-bottom: .75em; + padding: 10px; align-items: flex-end; - border-bottom: solid 0.5px var(--divider); - - &:last-child { - border-bottom: 0; - } + border-radius: 6px; /* (drag button) 32px + (drag button margin) 8px + (input width) 200px * 2 + (input gap) 12px = 452px */ @container (max-width: 452px) { diff --git a/packages/frontend/src/pages/settings/security.vue b/packages/frontend/src/pages/settings/security.vue index de0f63630e..8f9d4f858b 100644 --- a/packages/frontend/src/pages/settings/security.vue +++ b/packages/frontend/src/pages/settings/security.vue @@ -124,7 +124,7 @@ definePageMetadata(() => ({ } &:not(:last-child) { - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); } > header { @@ -136,11 +136,11 @@ definePageMetadata(() => ({ margin-right: 0.75em; &.succ { - color: var(--success); + color: var(--MI_THEME-success); } &.fail { - color: var(--error); + color: var(--MI_THEME-error); } } diff --git a/packages/frontend/src/pages/settings/sounds.sound.vue b/packages/frontend/src/pages/settings/sounds.sound.vue index 81478fede5..56f65e2309 100644 --- a/packages/frontend/src/pages/settings/sounds.sound.vue +++ b/packages/frontend/src/pages/settings/sounds.sound.vue @@ -194,7 +194,7 @@ function save() { flex-grow: 1; min-width: 0; font-weight: 700; - color: var(--error); + color: var(--MI_THEME-error); } .fileSelectorButton { @@ -203,6 +203,6 @@ function save() { .fileNotSelected { font-weight: 700; - color: var(--infoWarnFg); + color: var(--MI_THEME-infoWarnFg); } </style> diff --git a/packages/frontend/src/pages/settings/theme.vue b/packages/frontend/src/pages/settings/theme.vue index e7aef55a53..9f7842ecdc 100644 --- a/packages/frontend/src/pages/settings/theme.vue +++ b/packages/frontend/src/pages/settings/theme.vue @@ -175,7 +175,7 @@ definePageMetadata(() => ({ <style lang="scss" scoped> .rfqxtzch { - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); > .toggle { position: relative; @@ -204,7 +204,7 @@ definePageMetadata(() => ({ } .dn:focus-visible ~ .toggle { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: 2px; } @@ -227,12 +227,12 @@ definePageMetadata(() => ({ > .before { left: -70px; - color: var(--accent); + color: var(--MI_THEME-accent); } > .after { right: -68px; - color: var(--fg); + color: var(--MI_THEME-fg); } } @@ -255,7 +255,7 @@ definePageMetadata(() => ({ background-color: #E8CDA5; opacity: 0; transition: opacity 200ms ease-in-out !important; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); } .crater--1 { @@ -350,11 +350,11 @@ definePageMetadata(() => ({ background-color: #749DD6; > .before { - color: var(--fg); + color: var(--MI_THEME-fg); } > .after { - color: var(--accent); + color: var(--MI_THEME-accent); } .toggle__handler { @@ -405,14 +405,14 @@ definePageMetadata(() => ({ > .sync { padding: 14px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } } .rsljpzjq { > .selects { display: flex; - gap: 1.5em var(--margin); + gap: 1.5em var(--MI-margin); flex-wrap: wrap; > .select { diff --git a/packages/frontend/src/pages/settings/webhook.edit.vue b/packages/frontend/src/pages/settings/webhook.edit.vue index adeaf8550c..22b008fb61 100644 --- a/packages/frontend/src/pages/settings/webhook.edit.vue +++ b/packages/frontend/src/pages/settings/webhook.edit.vue @@ -44,7 +44,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkButton transparent :class="$style.testButton" :disabled="!(active && event_renote)" @click="test('renote')"><i class="ti ti-send"></i></MkButton> </div> <div :class="$style.switchBox"> - <MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> + <MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> <MkButton transparent :class="$style.testButton" :disabled="!(active && event_reaction)" @click="test('reaction')"><i class="ti ti-send"></i></MkButton> </div> <div :class="$style.switchBox"> @@ -184,6 +184,6 @@ definePageMetadata(() => ({ .description { font-size: 0.85em; padding: 8px 0 0 0; - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/pages/settings/webhook.new.vue b/packages/frontend/src/pages/settings/webhook.new.vue index d62357caaf..727c4df2d6 100644 --- a/packages/frontend/src/pages/settings/webhook.new.vue +++ b/packages/frontend/src/pages/settings/webhook.new.vue @@ -27,7 +27,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSwitch v-model="event_note">{{ i18n.ts._webhookSettings._events.note }}</MkSwitch> <MkSwitch v-model="event_reply">{{ i18n.ts._webhookSettings._events.reply }}</MkSwitch> <MkSwitch v-model="event_renote">{{ i18n.ts._webhookSettings._events.renote }}</MkSwitch> - <MkSwitch v-model="event_reaction">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> + <MkSwitch v-model="event_reaction" :disabled="true">{{ i18n.ts._webhookSettings._events.reaction }}</MkSwitch> <MkSwitch v-model="event_mention">{{ i18n.ts._webhookSettings._events.mention }}</MkSwitch> </div> </FormSection> diff --git a/packages/frontend/src/pages/settings/webhook.vue b/packages/frontend/src/pages/settings/webhook.vue index 0d11b00c97..af8b7ca945 100644 --- a/packages/frontend/src/pages/settings/webhook.vue +++ b/packages/frontend/src/pages/settings/webhook.vue @@ -17,8 +17,8 @@ SPDX-License-Identifier: AGPL-3.0-only <template #icon> <i v-if="webhook.active === false" class="ti ti-player-pause"></i> <i v-else-if="webhook.latestStatus === null" class="ti ti-circle"></i> - <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--success)' }"></i> - <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--error)' }"></i> + <i v-else-if="[200, 201, 204].includes(webhook.latestStatus)" class="ti ti-check" :style="{ color: 'var(--MI_THEME-success)' }"></i> + <i v-else class="ti ti-alert-triangle" :style="{ color: 'var(--MI_THEME-error)' }"></i> </template> {{ webhook.name || webhook.url }} <template #suffix> diff --git a/packages/frontend/src/pages/signup-complete.vue b/packages/frontend/src/pages/signup-complete.vue index 226f08bf8e..503e6e0f73 100644 --- a/packages/frontend/src/pages/signup-complete.vue +++ b/packages/frontend/src/pages/signup-complete.vue @@ -78,7 +78,7 @@ place-content: center; .form { position: relative; z-index: 10; - border-radius: var(--radius); + border-radius: var(--MI-radius); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); overflow: clip; max-width: 500px; @@ -88,7 +88,7 @@ place-content: center; padding: 16px; text-align: center; font-size: 26px; - background-color: var(--accentedBg); - color: var(--accent); + background-color: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); } </style> diff --git a/packages/frontend/src/pages/tag.vue b/packages/frontend/src/pages/tag.vue index 0d261b1af3..a45b61fb10 100644 --- a/packages/frontend/src/pages/tag.vue +++ b/packages/frontend/src/pages/tag.vue @@ -76,10 +76,10 @@ definePageMetadata(() => ({ <style lang="scss" module> .footer { - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - background: var(--acrylicBg); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + background: var(--MI_THEME-acrylicBg); + border-top: solid 0.5px var(--MI_THEME-divider); display: flex; } diff --git a/packages/frontend/src/pages/theme-editor.vue b/packages/frontend/src/pages/theme-editor.vue index 2fa6eb81ba..a530f4b5d6 100644 --- a/packages/frontend/src/pages/theme-editor.vue +++ b/packages/frontend/src/pages/theme-editor.vue @@ -236,7 +236,7 @@ definePageMetadata(() => ({ position: relative; width: 64px; height: 64px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); > .preview { position: absolute; @@ -247,7 +247,7 @@ definePageMetadata(() => ({ margin: auto; width: 42px; height: 42px; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); box-shadow: 0 2px 4px rgb(0 0 0 / 30%); transition: transform 0.15s ease; } @@ -259,14 +259,14 @@ definePageMetadata(() => ({ } &.active { - box-shadow: 0 0 0 2px var(--divider) inset; + box-shadow: 0 0 0 2px var(--MI_THEME-divider) inset; } &.rounded { - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); > .preview { - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } } diff --git a/packages/frontend/src/pages/timeline.vue b/packages/frontend/src/pages/timeline.vue index 60974da971..032a19a6eb 100644 --- a/packages/frontend/src/pages/timeline.vue +++ b/packages/frontend/src/pages/timeline.vue @@ -9,19 +9,20 @@ SPDX-License-Identifier: AGPL-3.0-only <MkSpacer :contentMax="800"> <MkHorizontalSwipe v-model:tab="src" :tabs="$i ? headerTabs : headerTabsWhenNotLogin"> <div :key="src" ref="rootEl"> - <MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--margin);" closable @close="closeTutorial()"> + <MkInfo v-if="isBasicTimeline(src) && !defaultStore.reactiveState.timelineTutorials.value[src]" style="margin-bottom: var(--MI-margin);" closable @close="closeTutorial()"> {{ i18n.ts._timelineDescription[src] }} </MkInfo> - <MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--margin);"/> + <MkPostForm v-if="defaultStore.reactiveState.showFixedPostForm.value" :class="$style.postForm" class="post-form _panel" fixed style="margin-bottom: var(--MI-margin);"/> <div v-if="queue > 0" :class="$style.new"><button class="_buttonPrimary" :class="$style.newButton" @click="top()">{{ i18n.ts.newNoteRecived }}</button></div> <div :class="$style.tl"> <MkTimeline ref="tlComponent" - :key="src + withRenotes + withBots + withReplies + onlyFiles" + :key="src + withRenotes + withBots + withReplies + onlyFiles + withSensitive" :src="src.split(':')[0]" :list="src.split(':')[1]" :withRenotes="withRenotes" :withReplies="withReplies" + :withSensitive="withSensitive" :onlyFiles="onlyFiles" :withBots="withBots" :sound="true" @@ -130,11 +131,6 @@ watch(src, () => { queue.value = 0; }); -watch(withSensitive, () => { - // ã“れã ã‘ã¯ã‚¯ãƒ©ã‚¤ã‚¢ãƒ³ãƒˆå´ã§å®Œçµã™ã‚‹å‡¦ç†ãªã®ã§æ‰‹å‹•ã§ãƒªãƒãƒ¼ãƒ‰ - tlComponent.value?.reloadTimeline(); -}); - function queueUpdated(q: number): void { queue.value = q; } @@ -363,30 +359,30 @@ definePageMetadata(() => ({ <style lang="scss" module> .new { position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); + top: calc(var(--MI-stickyTop, 0px) + 16px); z-index: 1000; width: 100%; margin: calc(-0.675em - 8px) 0; &:first-child { - margin-top: calc(-0.675em - 8px - var(--margin)); + margin-top: calc(-0.675em - 8px - var(--MI-margin)); } } .newButton { display: block; - margin: var(--margin) auto 0 auto; + margin: var(--MI-margin) auto 0 auto; padding: 8px 16px; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); } .postForm { - border-radius: var(--radius); + border-radius: var(--MI-radius); } .tl { - background: var(--bg); - border-radius: var(--radius); + background: var(--MI_THEME-bg); + border-radius: var(--MI-radius); overflow: clip; } </style> diff --git a/packages/frontend/src/pages/user-list-timeline.vue b/packages/frontend/src/pages/user-list-timeline.vue index 396e6eb14a..85e44b2503 100644 --- a/packages/frontend/src/pages/user-list-timeline.vue +++ b/packages/frontend/src/pages/user-list-timeline.vue @@ -114,26 +114,26 @@ definePageMetadata(() => ({ <style lang="scss" module> .new { position: sticky; - top: calc(var(--stickyTop, 0px) + 16px); + top: calc(var(--MI-stickyTop, 0px) + 16px); z-index: 1000; width: 100%; margin: calc(-0.675em - 8px) 0; &:first-child { - margin-top: calc(-0.675em - 8px - var(--margin)); + margin-top: calc(-0.675em - 8px - var(--MI-margin)); } } .newButton { display: block; - margin: var(--margin) auto 0 auto; + margin: var(--MI-margin) auto 0 auto; padding: 8px 16px; - border-radius: var(--radius-xl); + border-radius: var(--MI-radius-xl); } .tl { - background: var(--bg); - border-radius: var(--radius); + background: var(--MI_THEME-bg); + border-radius: var(--MI-radius); overflow: clip; } </style> diff --git a/packages/frontend/src/pages/user/clips.vue b/packages/frontend/src/pages/user/clips.vue index ac01cff8cd..38ce78e8d5 100644 --- a/packages/frontend/src/pages/user/clips.vue +++ b/packages/frontend/src/pages/user/clips.vue @@ -43,6 +43,6 @@ const pagination = { .description { margin-top: 8px; padding-top: 8px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/pages/user/follow-list.vue b/packages/frontend/src/pages/user/follow-list.vue index e60dccec17..868767e8f4 100644 --- a/packages/frontend/src/pages/user/follow-list.vue +++ b/packages/frontend/src/pages/user/follow-list.vue @@ -45,6 +45,6 @@ const followersPagination = { .users { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); - grid-gap: var(--margin); + grid-gap: var(--MI-margin); } </style> diff --git a/packages/frontend/src/pages/user/gallery.vue b/packages/frontend/src/pages/user/gallery.vue index 9ba81322ba..0bc5628528 100644 --- a/packages/frontend/src/pages/user/gallery.vue +++ b/packages/frontend/src/pages/user/gallery.vue @@ -38,6 +38,6 @@ const pagination = { display: grid; grid-template-columns: repeat(auto-fill, minmax(260px, 1fr)); grid-gap: 12px; - margin: var(--margin); + margin: var(--MI-margin); } </style> diff --git a/packages/frontend/src/pages/user/home.vue b/packages/frontend/src/pages/user/home.vue index 7bb8c3fd3d..5565555ca4 100644 --- a/packages/frontend/src/pages/user/home.vue +++ b/packages/frontend/src/pages/user/home.vue @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only <MkUserName class="name" :user="user" :nowrap="true"/> <div class="bottom"> <span class="username"><MkAcct :user="user" :detail="true"/></span> - <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> + <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span> <span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span> <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> <button v-if="$i && !isEditingMemo && !memoDraft" class="_button add-note-button" @click="showMemoTextarea"> @@ -49,15 +49,16 @@ SPDX-License-Identifier: AGPL-3.0-only <MkUserName :user="user" :nowrap="false" class="name"/> <div class="bottom"> <span class="username"><MkAcct :user="user" :detail="true"/></span> - <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--badge);"><i class="ti ti-shield"></i></span> + <span v-if="user.isAdmin" :title="i18n.ts.isAdmin" style="color: var(--MI_THEME-badge);"><i class="ti ti-shield"></i></span> <span v-if="user.isLocked" :title="i18n.ts.isLocked"><i class="ti ti-lock"></i></span> <span v-if="user.isBot" :title="i18n.ts.isBot"><i class="ti ti-robot"></i></span> </div> </div> <div v-if="user.followedMessage != null" class="followedMessage"> - <div style="border: solid 1px var(--love); border-radius: 6px; background: color-mix(in srgb, var(--love), transparent 90%); padding: 6px 8px;"> - <Mfm :text="user.followedMessage" :author="user"/> - </div> + <MkFukidashi class="fukidashi" :tail="narrow ? 'none' : 'left'" negativeMargin shadow> + <div class="messageHeader">{{ i18n.ts.messageToFollower }}</div> + <div><MkSparkle><Mfm :plain="true" :text="user.followedMessage" :author="user"/></MkSparkle></div> + </MkFukidashi> </div> <div v-if="user.roles.length > 0" class="roles"> <span v-for="role in user.roles" :key="role.id" v-tooltip="role.description" class="role" :style="{ '--color': role.color }"> @@ -70,6 +71,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="iAmModerator" class="moderationNote"> <MkTextarea v-if="editModerationNote || (moderationNote != null && moderationNote !== '')" v-model="moderationNote" manualSave> <template #label>{{ i18n.ts.moderationNote }}</template> + <template #caption>{{ i18n.ts.moderationNoteDescription }}</template> </MkTextarea> <div v-else> <MkButton small @click="editModerationNote = true">{{ i18n.ts.addModerationNote }}</MkButton> @@ -190,15 +192,16 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, computed, onMounted, onUnmounted, nextTick, watch, ref } from 'vue'; import * as Misskey from 'misskey-js'; +import { getScrollPosition } from '@@/js/scroll.js'; import MkTab from '@/components/MkTab.vue'; import MkNotes from '@/components/MkNotes.vue'; import MkFollowButton from '@/components/MkFollowButton.vue'; import MkAccountMoved from '@/components/MkAccountMoved.vue'; +import MkFukidashi from '@/components/MkFukidashi.vue'; import MkRemoteCaution from '@/components/MkRemoteCaution.vue'; import MkTextarea from '@/components/MkTextarea.vue'; import MkInfo from '@/components/MkInfo.vue'; import MkButton from '@/components/MkButton.vue'; -import { getScrollPosition } from '@@/js/scroll.js'; import { getUserMenu } from '@/scripts/get-user-menu.js'; import number from '@/filters/number.js'; import { userPage } from '@/filters/user.js'; @@ -213,11 +216,12 @@ import { isFollowingVisibleForMe, isFollowersVisibleForMe } from '@/scripts/isFf import { useRouter } from '@/router/supplier.js'; import { getStaticImageUrl } from '@/scripts/media-proxy.js'; import { infoImageUrl } from '@/instance.js'; +import MkSparkle from '@/components/MkSparkle.vue'; const MkNote = defineAsyncComponent(() => defaultStore.state.noteDesign === 'sharkey' - ? import('@/components/SkNote.vue') - : import('@/components/MkNote.vue'), + ? import('@/components/SkNote.vue') + : import('@/components/MkNote.vue'), ); function calcAge(birthdate: string): number { @@ -347,7 +351,7 @@ function parallaxLoop() { } function parallax() { - const banner = bannerEl.value as any; + const banner = bannerEl.value; if (banner == null) return; const top = getScrollPosition(rootEl.value); @@ -419,7 +423,7 @@ onUnmounted(() => { background-size: cover; background-position: center; pointer-events: none; - filter: var(--blur, blur(10px)) opacity(0.6); + filter: var(--MI-blur, blur(10px)) opacity(0.6); // Funny CSS schenanigans to make background escape container left: -100%; top: -5%; @@ -442,7 +446,7 @@ onUnmounted(() => { > .main { position: relative; overflow: clip; - background: color-mix(in srgb, var(--panel) 65%, transparent); + background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); > .banner-container { position: relative; @@ -473,11 +477,11 @@ onUnmounted(() => { position: absolute; top: 12px; right: 12px; - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); background: rgba(0, 0, 0, 0.2); padding: 8px; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); > .menu { vertical-align: bottom; @@ -528,9 +532,9 @@ onUnmounted(() => { > .add-note-button { background: rgba(0, 0, 0, 0.2); color: #fff; - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); - border-radius: var(--radius-lg); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); + border-radius: var(--MI-radius-lg); padding: 4px 8px; font-size: 80%; } @@ -543,7 +547,7 @@ onUnmounted(() => { text-align: center; padding: 50px 8px 16px 8px; font-weight: bold; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); > .bottom { > * { @@ -567,7 +571,18 @@ onUnmounted(() => { > .followedMessage { padding: 24px 24px 0 154px; - font-size: 0.9em; + + > .fukidashi { + display: block; + --fukidashi-bg: color-mix(in srgb, var(--MI_THEME-accent), var(--MI_THEME-panel) 85%); + --fukidashi-radius: 16px; + font-size: 0.9em; + + .messageHeader { + opacity: 0.7; + font-size: 0.85em; + } + } } > .roles { @@ -578,8 +593,8 @@ onUnmounted(() => { gap: 8px; > .role { - border: solid 1px var(--color, var(--divider)); - border-radius: var(--radius-ellipse); + border: solid 1px var(--color, var(--MI_THEME-divider)); + border-radius: var(--MI-radius-ellipse); margin-right: 4px; padding: 3px 8px; } @@ -592,15 +607,15 @@ onUnmounted(() => { > .memo { margin: 12px 24px 0 154px; background: transparent; - color: var(--fg); - border: 1px solid var(--divider); - border-radius: var(--radius-sm); + color: var(--MI_THEME-fg); + border: 1px solid var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); padding: 8px; line-height: 0; > .heading { text-align: left; - color: var(--fgTransparent); + color: var(--MI_THEME-fgTransparent); line-height: 1.5; font-size: 85%; } @@ -615,7 +630,7 @@ onUnmounted(() => { height: auto; min-height: 0; line-height: 1.5; - color: var(--fg); + color: var(--MI_THEME-fg); overflow: hidden; background: transparent; font-family: inherit; @@ -635,7 +650,7 @@ onUnmounted(() => { > .fields { padding: 24px; font-size: 0.9em; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); > .field { display: flex; @@ -672,14 +687,14 @@ onUnmounted(() => { > .status { display: flex; padding: 24px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); > a { flex: 1; text-align: center; &.active { - color: var(--accent); + color: var(--MI_THEME-accent); } &:hover { @@ -701,7 +716,7 @@ onUnmounted(() => { > .contents { > .content { - margin-bottom: var(--margin); + margin-bottom: var(--MI-margin); } } } @@ -718,7 +733,7 @@ onUnmounted(() => { > .sub { max-width: 350px; min-width: 350px; - margin-left: var(--margin); + margin-left: var(--MI-margin); } } } @@ -796,7 +811,7 @@ onUnmounted(() => { <style lang="scss" module> .tl { background-color: rgba(0, 0, 0, 0); - border-radius: var(--radius); + border-radius: var(--MI-radius); overflow: clip; z-index: 0; } @@ -804,12 +819,12 @@ onUnmounted(() => { .tab { margin-bottom: calc(var(--margin) / 2); padding: calc(var(--margin) / 2) 0; - background: color-mix(in srgb, var(--bg) 65%, transparent); - backdrop-filter: var(--blur, blur(15px)); - border-radius: var(--radius-sm); + background: color-mix(in srgb, var(--MI_THEME-bg) 65%, transparent); + backdrop-filter: var(--MI-blur, blur(15px)); + border-radius: var(--MI-radius-sm); > button { - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); margin-left: 0.4rem; margin-right: 0.4rem; } @@ -817,7 +832,7 @@ onUnmounted(() => { .verifiedLink { margin-left: 4px; - color: var(--success); + color: var(--MI_THEME-success); } .infoBadges { @@ -836,7 +851,7 @@ onUnmounted(() => { color: #fff; background: rgba(0, 0, 0, 0.7); font-size: 0.7em; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); list-style-type: none; margin-left: 0; } diff --git a/packages/frontend/src/pages/user/index.files.vue b/packages/frontend/src/pages/user/index.files.vue index 23fd4ca23e..7fe90da865 100644 --- a/packages/frontend/src/pages/user/index.files.vue +++ b/packages/frontend/src/pages/user/index.files.vue @@ -96,7 +96,7 @@ onMounted(() => { .img { position: relative; height: 128px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; } diff --git a/packages/frontend/src/pages/user/index.timeline.vue b/packages/frontend/src/pages/user/index.timeline.vue index 28a5091059..3aa9b10214 100644 --- a/packages/frontend/src/pages/user/index.timeline.vue +++ b/packages/frontend/src/pages/user/index.timeline.vue @@ -69,13 +69,13 @@ const pagination = computed(() => tab.value === 'featured' ? { <style lang="scss" module> .tab { - padding: calc(var(--margin) / 2) 0; - background: var(--bg); + padding: calc(var(--MI-margin) / 2) 0; + background: var(--MI_THEME-bg); } .tl { - background: var(--bg); - border-radius: var(--radius); + background: var(--MI_THEME-bg); + border-radius: var(--MI-radius); overflow: clip; } </style> diff --git a/packages/frontend/src/pages/user/index.vue b/packages/frontend/src/pages/user/index.vue index 87c8bb2866..a35250bf5f 100644 --- a/packages/frontend/src/pages/user/index.vue +++ b/packages/frontend/src/pages/user/index.vue @@ -39,6 +39,7 @@ import { definePageMetadata } from '@/scripts/page-metadata.js'; import { i18n } from '@/i18n.js'; import { $i } from '@/account.js'; import MkHorizontalSwipe from '@/components/MkHorizontalSwipe.vue'; +import { getServerContext } from '@/server-context.js'; const XHome = defineAsyncComponent(() => import('./home.vue')); const XTimeline = defineAsyncComponent(() => import('./index.timeline.vue')); @@ -52,6 +53,8 @@ const XFlashs = defineAsyncComponent(() => import('./flashs.vue')); const XGallery = defineAsyncComponent(() => import('./gallery.vue')); const XRaw = defineAsyncComponent(() => import('./raw.vue')); +const CTX_USER = getServerContext('user'); + const props = withDefaults(defineProps<{ acct: string; page?: string; @@ -61,13 +64,24 @@ const props = withDefaults(defineProps<{ const tab = ref(props.page); -const user = ref<null | Misskey.entities.UserDetailed>(null); +const user = ref<null | Misskey.entities.UserDetailed>(CTX_USER); const error = ref<any>(null); function fetchUser(): void { if (props.acct == null) return; + + const { username, host } = Misskey.acct.parse(props.acct); + + if (CTX_USER && CTX_USER.username === username && CTX_USER.host === host) { + user.value = CTX_USER; + return; + } + user.value = null; - misskeyApi('users/show', Misskey.acct.parse(props.acct)).then(u => { + misskeyApi('users/show', { + username, + host, + }).then(u => { user.value = u; }).catch(err => { error.value = err; diff --git a/packages/frontend/src/pages/user/lists.vue b/packages/frontend/src/pages/user/lists.vue index 8f95ce2dc9..a3d1974ced 100644 --- a/packages/frontend/src/pages/user/lists.vue +++ b/packages/frontend/src/pages/user/lists.vue @@ -44,12 +44,12 @@ const pagination = { .list { display: block; padding: 16px; - border: solid 1px var(--divider); - border-radius: var(--radius-sm); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius-sm); margin-bottom: 8px; &:hover { - border: solid 1px var(--accent); + border: solid 1px var(--MI_THEME-accent); text-decoration: none; } } diff --git a/packages/frontend/src/pages/user/raw.vue b/packages/frontend/src/pages/user/raw.vue index ac18ad9392..f24a215afc 100644 --- a/packages/frontend/src/pages/user/raw.vue +++ b/packages/frontend/src/pages/user/raw.vue @@ -107,24 +107,24 @@ const suspended = computed(() => props.user.isSuspended ?? false); > .moderator { display: inline-block; border: solid 1px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); padding: 2px 6px; font-size: 85%; } > .suspended { - color: var(--error); - border-color: var(--error); + color: var(--MI_THEME-error); + border-color: var(--MI_THEME-error); } > .silenced { - color: var(--warn); - border-color: var(--warn); + color: var(--MI_THEME-warn); + border-color: var(--MI_THEME-warn); } > .moderator { - color: var(--success); - border-color: var(--success); + color: var(--MI_THEME-success); + border-color: var(--MI_THEME-success); } } </style> diff --git a/packages/frontend/src/pages/user/reactions.vue b/packages/frontend/src/pages/user/reactions.vue index 3671decc18..7168778e12 100644 --- a/packages/frontend/src/pages/user/reactions.vue +++ b/packages/frontend/src/pages/user/reactions.vue @@ -44,7 +44,7 @@ const pagination = { align-items: center; padding: 8px 16px; margin-bottom: 8px; - border-bottom: solid 2px var(--divider); + border-bottom: solid 2px var(--MI_THEME-divider); } .avatar { diff --git a/packages/frontend/src/pages/welcome.entrance.a.vue b/packages/frontend/src/pages/welcome.entrance.a.vue index 0d132e6a86..f1842255e0 100644 --- a/packages/frontend/src/pages/welcome.entrance.a.vue +++ b/packages/frontend/src/pages/welcome.entrance.a.vue @@ -98,7 +98,7 @@ misskeyApiGet('federation/instances', { left: 0; width: 100vw; height: 100vh; - background: var(--accent); + background: var(--MI_THEME-accent); clip-path: polygon(0% 0%, 45% 0%, 20% 100%, 0% 100%); } > .shape2 { @@ -107,7 +107,7 @@ misskeyApiGet('federation/instances', { left: 0; width: 100vw; height: 100vh; - background: var(--accent); + background: var(--MI_THEME-accent); clip-path: polygon(0% 0%, 25% 0%, 35% 100%, 0% 100%); opacity: 0.5; } @@ -164,10 +164,10 @@ misskeyApiGet('federation/instances', { left: 0; right: 0; margin: auto; - background: var(--acrylicPanel); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-acrylicPanel); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); + border-radius: var(--MI-radius-ellipse); overflow: clip; width: 800px; padding: 8px 0; @@ -186,15 +186,15 @@ misskeyApiGet('federation/instances', { vertical-align: bottom; padding: 6px 12px 6px 6px; margin: 0 10px 0 0; - background: var(--panel); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-panel); + border-radius: var(--MI-radius-ellipse); > :global(.icon) { display: inline-block; width: 20px; height: 20px; margin-right: 5px; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); } } </style> diff --git a/packages/frontend/src/pages/welcome.setup.vue b/packages/frontend/src/pages/welcome.setup.vue index 5a41100bf1..58e815d89c 100644 --- a/packages/frontend/src/pages/welcome.setup.vue +++ b/packages/frontend/src/pages/welcome.setup.vue @@ -14,6 +14,10 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div class="_gaps_m" style="padding: 32px;"> <div>{{ i18n.ts.intro }}</div> + <MkInput v-model="setupPassword" type="password" data-cy-admin-initial-password> + <template #label>{{ i18n.ts.initialPasswordForSetup }} <div v-tooltip:dialog="i18n.ts.initialPasswordForSetupDescription" class="_button _help"><i class="ti ti-help-circle"></i></div></template> + <template #prefix><i class="ti ti-lock"></i></template> + </MkInput> <MkInput v-model="username" pattern="^[a-zA-Z0-9_]{1,20}$" :spellcheck="false" required data-cy-admin-username> <template #label>{{ i18n.ts.username }}</template> <template #prefix>@</template> @@ -36,9 +40,9 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { ref } from 'vue'; +import { host, version } from '@@/js/config.js'; import MkButton from '@/components/MkButton.vue'; import MkInput from '@/components/MkInput.vue'; -import { host, version } from '@@/js/config.js'; import * as os from '@/os.js'; import { misskeyApi } from '@/scripts/misskey-api.js'; import { login } from '@/account.js'; @@ -47,6 +51,7 @@ import MkAnimBg from '@/components/MkAnimBg.vue'; const username = ref(''); const password = ref(''); +const setupPassword = ref(''); const submitting = ref(false); function submit() { @@ -56,14 +61,27 @@ function submit() { misskeyApi('admin/accounts/create', { username: username.value, password: password.value, + setupPassword: setupPassword.value === '' ? null : setupPassword.value, }).then(res => { return login(res.token); - }).catch(() => { + }).catch((err) => { submitting.value = false; + let title = i18n.ts.somethingHappened; + let text = err.message + '\n' + err.id; + + if (err.code === 'ACCESS_DENIED') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.operationForbidden; + } else if (err.code === 'INCORRECT_INITIAL_PASSWORD') { + title = i18n.ts.permissionDeniedError; + text = i18n.ts.incorrectPassword; + } + os.alert({ type: 'error', - text: i18n.ts.somethingHappened, + title, + text, }); }); } @@ -74,14 +92,14 @@ function submit() { min-height: 100svh; padding: 32px 32px 64px 32px; box-sizing: border-box; -display: grid; -place-content: center; + display: grid; + place-content: center; } .form { position: relative; z-index: 10; - border-radius: var(--radius); + border-radius: var(--MI-radius); box-shadow: 0 8px 16px rgba(0, 0, 0, 0.1); overflow: clip; max-width: 500px; @@ -92,8 +110,8 @@ place-content: center; font-size: 1.5em; text-align: center; padding: 32px; - background: var(--accentedBg); - color: var(--accent); + background: var(--MI_THEME-accentedBg); + color: var(--MI_THEME-accent); font-weight: bold; } diff --git a/packages/frontend/src/pages/welcome.timeline.note.vue b/packages/frontend/src/pages/welcome.timeline.note.vue index ee8d4e1d62..460a225f23 100644 --- a/packages/frontend/src/pages/welcome.timeline.note.vue +++ b/packages/frontend/src/pages/welcome.timeline.note.vue @@ -84,7 +84,7 @@ onUpdated(() => { left: 0; width: 100%; height: 64px; - background: linear-gradient(0deg, var(--panel), color(from var(--panel) srgb r g b / 0)); + background: linear-gradient(0deg, var(--MI_THEME-panel), color(from var(--MI_THEME-panel) srgb r g b / 0)); } } @@ -92,7 +92,7 @@ onUpdated(() => { padding: 16px; margin: 0 0 0 auto; max-width: max-content; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } .reactions { @@ -100,7 +100,7 @@ onUpdated(() => { margin: 8px -16px -8px; padding: 8px 16px 0; width: calc(100% + 32px); - border-top: 1px solid var(--divider); + border-top: 1px solid var(--MI_THEME-divider); } .richcontent { diff --git a/packages/frontend/src/pages/welcome.timeline.vue b/packages/frontend/src/pages/welcome.timeline.vue index 16d558cc91..2964b15164 100644 --- a/packages/frontend/src/pages/welcome.timeline.vue +++ b/packages/frontend/src/pages/welcome.timeline.vue @@ -60,7 +60,7 @@ onUpdated(() => { transform: translate3d(0, 0, 0); } 100% { - transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0); + transform: translate3d(0, calc(calc(-100% - 128px) - var(--MI-margin)), 0); } } @@ -69,7 +69,7 @@ onUpdated(() => { transform: translate3d(0, -128px, 0); } 100% { - transform: translate3d(0, calc(calc(-100% - 128px) - var(--margin)), 0); + transform: translate3d(0, calc(calc(-100% - 128px) - var(--MI-margin)), 0); } } diff --git a/packages/frontend/src/pizzax.ts b/packages/frontend/src/pizzax.ts index ac325e923f..7740fe0d39 100644 --- a/packages/frontend/src/pizzax.ts +++ b/packages/frontend/src/pizzax.ts @@ -241,9 +241,13 @@ export class Storage<T extends StateDef> { * 特定ã®ã‚ーã®ã€ç°¡æ˜“çš„ãªgetter/setterを作りã¾ã™ * 主ã«vue上ã§è¨å®šã‚³ãƒ³ãƒˆãƒãƒ¼ãƒ«ã®modelã¨ã—ã¦ä½¿ã†ç”¨ */ - public makeGetterSetter<K extends keyof T>(key: K, getter?: (v: T[K]) => unknown, setter?: (v: unknown) => T[K]): { - get: () => T[K]['default']; - set: (value: T[K]['default']) => void; + public makeGetterSetter<K extends keyof T, R = T[K]['default']>( + key: K, + getter?: (v: T[K]['default']) => R, + setter?: (v: R) => T[K]['default'], + ): { + get: () => R; + set: (value: R) => void; } { const valueRef = ref(this.state[key]); @@ -265,7 +269,7 @@ export class Storage<T extends StateDef> { return valueRef.value; } }, - set: (value: unknown) => { + set: (value) => { const val = setter ? setter(value) : value; this.set(key, val); valueRef.value = val; diff --git a/packages/frontend/src/router/definition.ts b/packages/frontend/src/router/definition.ts index 686ac6920a..c7637a1db9 100644 --- a/packages/frontend/src/router/definition.ts +++ b/packages/frontend/src/router/definition.ts @@ -10,7 +10,7 @@ import { $i, iAmModerator } from '@/account.js'; import MkLoading from '@/pages/_loading_.vue'; import MkError from '@/pages/_error_.vue'; -export const page = (loader: AsyncComponentLoader<any>) => defineAsyncComponent({ +export const page = (loader: AsyncComponentLoader) => defineAsyncComponent({ loader: loader, loadingComponent: MkLoading, errorComponent: MkError, @@ -217,7 +217,7 @@ const routes: RouteDef[] = [{ component: page(() => import('@/pages/theme-editor.vue')), loginRequired: true, }, { - path: '/roles/:role', + path: '/roles/:roleId', component: page(() => import('@/pages/role.vue')), }, { path: '/user-tags/:tag', diff --git a/packages/frontend/src/router/main.ts b/packages/frontend/src/router/main.ts index 709c508741..8307df1150 100644 --- a/packages/frontend/src/router/main.ts +++ b/packages/frontend/src/router/main.ts @@ -4,7 +4,7 @@ */ import { EventEmitter } from 'eventemitter3'; -import { IRouter, Resolved, RouteDef, RouterEvent } from '@/nirax.js'; +import { IRouter, Resolved, RouteDef, RouterEvent, RouterFlag } from '@/nirax.js'; import type { App, ShallowRef } from 'vue'; @@ -83,7 +83,7 @@ class MainRouterProxy implements IRouter { return this.supplier().currentRoute; } - get navHook(): ((path: string, flag?: any) => boolean) | null { + get navHook(): ((path: string, flag?: RouterFlag) => boolean) | null { return this.supplier().navHook; } @@ -95,11 +95,11 @@ class MainRouterProxy implements IRouter { return this.supplier().getCurrentKey(); } - getCurrentPath(): any { + getCurrentPath(): string { return this.supplier().getCurrentPath(); } - push(path: string, flag?: any): void { + push(path: string, flag?: RouterFlag): void { this.supplier().push(path, flag); } diff --git a/packages/frontend/src/scripts/check-word-mute.ts b/packages/frontend/src/scripts/check-word-mute.ts index e65c327ffe..6525c207f7 100644 --- a/packages/frontend/src/scripts/check-word-mute.ts +++ b/packages/frontend/src/scripts/check-word-mute.ts @@ -2,10 +2,9 @@ * SPDX-FileCopyrightText: syuilo and misskey-project * SPDX-License-Identifier: AGPL-3.0-only */ +import * as Misskey from 'misskey-js'; -import type { Note, MeDetailed } from "misskey-js/entities.js"; - -export function checkWordMute(note: Note, me: MeDetailed | null | undefined, mutedWords: Array<string | string[]>): boolean { +export function checkWordMute(note: Misskey.entities.Note, me: Misskey.entities.UserLite | null | undefined, mutedWords: Array<string | string[]>): boolean { // 自分自身 if (me && (note.userId === me.id)) return false; diff --git a/packages/frontend/src/scripts/device-kind.ts b/packages/frontend/src/scripts/device-kind.ts index 7c33f8ccee..7aadb617ca 100644 --- a/packages/frontend/src/scripts/device-kind.ts +++ b/packages/frontend/src/scripts/device-kind.ts @@ -3,22 +3,22 @@ * SPDX-License-Identifier: AGPL-3.0-only */ -import { defaultStore } from '@/store.js'; - -await defaultStore.ready; +export type DeviceKind = 'smartphone' | 'tablet' | 'desktop'; const ua = navigator.userAgent.toLowerCase(); const isTablet = /ipad/.test(ua) || (/mobile|iphone|android/.test(ua) && window.innerWidth > 700); const isSmartphone = !isTablet && /mobile|iphone|android/.test(ua); -const isIPhone = /iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1; -// navigator.platform may be deprecated but this check is still required -const isIPadOS = navigator.platform === 'MacIntel' && navigator.maxTouchPoints > 1; -const isIos = /ipad|iphone|ipod/gi.test(ua) && navigator.maxTouchPoints > 1; +export const DEFAULT_DEVICE_KIND: DeviceKind = ( + isSmartphone + ? 'smartphone' + : isTablet + ? 'tablet' + : 'desktop' +); -export const isFullscreenNotSupported = isIPhone || isIos; +export let deviceKind: DeviceKind = DEFAULT_DEVICE_KIND; -export const deviceKind: 'smartphone' | 'tablet' | 'desktop' = defaultStore.state.overridedDeviceKind ? defaultStore.state.overridedDeviceKind - : isSmartphone ? 'smartphone' - : isTablet ? 'tablet' - : 'desktop'; +export function updateDeviceKind(kind: DeviceKind | null) { + deviceKind = kind ?? DEFAULT_DEVICE_KIND; +} diff --git a/packages/frontend/src/scripts/favicon-dot.ts b/packages/frontend/src/scripts/favicon-dot.ts index a5972276e2..1f62c9ca70 100644 --- a/packages/frontend/src/scripts/favicon-dot.ts +++ b/packages/frontend/src/scripts/favicon-dot.ts @@ -78,7 +78,7 @@ class FavIconDot { this.ctx.beginPath(); this.ctx.arc(this.faviconImage.width - 10, 10, 10, 0, 2 * Math.PI); const computedStyle = getComputedStyle(document.documentElement); - this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--navIndicator')).toHexString(); + this.ctx.fillStyle = tinycolor(computedStyle.getPropertyValue('--MI_THEME-navIndicator')).toHexString(); this.ctx.strokeStyle = 'white'; this.ctx.fill(); this.ctx.stroke(); @@ -104,7 +104,7 @@ class FavIconDot { this.drawDot(); this.canvas.toDataURL('image/png'); } catch (error) { - return false; + return false; } return true; } @@ -140,6 +140,6 @@ export async function worksOnInstance() { icon = new FavIconDot(); await icon.setup(); } - + return await icon.worksOnInstance(); } diff --git a/packages/frontend/src/scripts/form.ts b/packages/frontend/src/scripts/form.ts index 242a504c3b..1032e97ac9 100644 --- a/packages/frontend/src/scripts/form.ts +++ b/packages/frontend/src/scripts/form.ts @@ -15,7 +15,7 @@ type Hidden = boolean | ((v: any) => boolean); export type FormItem = { label?: string; type: 'string'; - default: string | null; + default?: string | null; description?: string; required?: boolean; hidden?: Hidden; @@ -24,7 +24,7 @@ export type FormItem = { } | { label?: string; type: 'number'; - default: number | null; + default?: number | null; description?: string; required?: boolean; hidden?: Hidden; @@ -32,20 +32,20 @@ export type FormItem = { } | { label?: string; type: 'boolean'; - default: boolean | null; + default?: boolean | null; description?: string; hidden?: Hidden; } | { label?: string; type: 'enum'; - default: string | null; + default?: string | null; required?: boolean; hidden?: Hidden; enum: EnumItem[]; } | { label?: string; type: 'radio'; - default: unknown | null; + default?: unknown | null; required?: boolean; hidden?: Hidden; options: { @@ -55,7 +55,7 @@ export type FormItem = { } | { label?: string; type: 'range'; - default: number | null; + default?: number | null; description?: string; required?: boolean; step?: number; @@ -66,12 +66,12 @@ export type FormItem = { } | { label?: string; type: 'object'; - default: Record<string, unknown> | null; + default?: Record<string, unknown> | null; hidden: Hidden; } | { label?: string; type: 'array'; - default: unknown[] | null; + default?: unknown[] | null; hidden: Hidden; } | { type: 'button'; diff --git a/packages/frontend/src/scripts/fullscreen.ts b/packages/frontend/src/scripts/fullscreen.ts new file mode 100644 index 0000000000..7a0a018ef3 --- /dev/null +++ b/packages/frontend/src/scripts/fullscreen.ts @@ -0,0 +1,46 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +type PartiallyPartial<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>; + +type VideoEl = PartiallyPartial<HTMLVideoElement, 'requestFullscreen'> & { + webkitEnterFullscreen?(): void; + webkitExitFullscreen?(): void; +}; + +type PlayerEl = PartiallyPartial<HTMLElement, 'requestFullscreen'>; + +type RequestFullscreenProps = { + readonly videoEl: VideoEl; + readonly playerEl: PlayerEl; + readonly options?: FullscreenOptions | null; +}; + +type ExitFullscreenProps = { + readonly videoEl: VideoEl; +}; + +export const requestFullscreen = ({ videoEl, playerEl, options }: RequestFullscreenProps) => { + if (playerEl.requestFullscreen != null) { + playerEl.requestFullscreen(options ?? undefined); + return; + } + if (videoEl.webkitEnterFullscreen != null) { + videoEl.webkitEnterFullscreen(); + return; + } +}; + +export const exitFullscreen = ({ videoEl }: ExitFullscreenProps) => { + // eslint-disable-next-line @typescript-eslint/no-unnecessary-condition + if (document.exitFullscreen != null) { + document.exitFullscreen(); + return; + } + if (videoEl.webkitExitFullscreen != null) { + videoEl.webkitExitFullscreen(); + return; + } +}; diff --git a/packages/frontend/src/scripts/get-bg-color.ts b/packages/frontend/src/scripts/get-bg-color.ts new file mode 100644 index 0000000000..ccf60b454f --- /dev/null +++ b/packages/frontend/src/scripts/get-bg-color.ts @@ -0,0 +1,18 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import tinycolor from 'tinycolor2'; + +export const getBgColor = (elem?: Element | null | undefined): string | null => { + if (elem == null) return null; + + const { backgroundColor: bg } = window.getComputedStyle(elem); + + if (bg && tinycolor(bg).getAlpha() !== 0) { + return bg; + } + + return getBgColor(elem.parentElement); +}; diff --git a/packages/frontend/src/scripts/get-note-menu.ts b/packages/frontend/src/scripts/get-note-menu.ts index 4f3fb65665..c56fd185b6 100644 --- a/packages/frontend/src/scripts/get-note-menu.ts +++ b/packages/frontend/src/scripts/get-note-menu.ts @@ -267,13 +267,10 @@ export function getNoteMenu(props: { function togglePin(pin: boolean): void { os.apiWithDialog(pin ? 'i/pin' : 'i/unpin', { noteId: appearNote.id, - }, undefined, null, res => { - if (res.id === '72dab508-c64d-498f-8740-a8eec1ba385a') { - os.alert({ - type: 'error', - text: i18n.ts.pinLimitExceeded, - }); - } + }, undefined, { + '72dab508-c64d-498f-8740-a8eec1ba385a': { + text: i18n.ts.pinLimitExceeded, + }, }); } diff --git a/packages/frontend/src/scripts/init-chart.ts b/packages/frontend/src/scripts/init-chart.ts index 2465a14703..41e1636aa7 100644 --- a/packages/frontend/src/scripts/init-chart.ts +++ b/packages/frontend/src/scripts/init-chart.ts @@ -50,7 +50,7 @@ export function initChart() { ); // フォントカラー - Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--fg'); + Chart.defaults.color = getComputedStyle(document.documentElement).getPropertyValue('--MI_THEME-fg'); Chart.defaults.borderColor = defaultStore.state.darkMode ? 'rgba(255, 255, 255, 0.1)' : 'rgba(0, 0, 0, 0.1)'; diff --git a/packages/frontend/src/scripts/misskey-api.ts b/packages/frontend/src/scripts/misskey-api.ts index 1b1159fd01..e7a92e2d5c 100644 --- a/packages/frontend/src/scripts/misskey-api.ts +++ b/packages/frontend/src/scripts/misskey-api.ts @@ -17,7 +17,7 @@ export function misskeyApi< _ResT = ResT extends void ? Misskey.api.SwitchCaseResponseType<E, P> : ResT, >( endpoint: E, - data: P = {} as any, + data: P & { i?: string | null; } = {} as any, token?: string | null | undefined, signal?: AbortSignal, ): Promise<_ResT> { @@ -30,8 +30,8 @@ export function misskeyApi< const promise = new Promise<_ResT>((resolve, reject) => { // Append a credential - if ($i) (data as any).i = $i.token; - if (token !== undefined) (data as any).i = token; + if ($i) data.i = $i.token; + if (token !== undefined) data.i = token; // Send request window.fetch(`${apiUrl}/${endpoint}`, { diff --git a/packages/frontend/src/scripts/please-login.ts b/packages/frontend/src/scripts/please-login.ts index 18f05bc7f4..43dcf11936 100644 --- a/packages/frontend/src/scripts/please-login.ts +++ b/packages/frontend/src/scripts/please-login.ts @@ -44,17 +44,21 @@ export type OpenOnRemoteOptions = { params: Record<string, string>; }; -export function pleaseLogin(path?: string, openOnRemote?: OpenOnRemoteOptions) { +export function pleaseLogin(opts: { + path?: string; + message?: string; + openOnRemote?: OpenOnRemoteOptions; +} = {}) { if ($i) return; const { dispose } = popup(defineAsyncComponent(() => import('@/components/MkSigninDialog.vue')), { autoSet: true, - message: openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired, - openOnRemote, + message: opts.message ?? (opts.openOnRemote ? i18n.ts.signinOrContinueOnRemote : i18n.ts.signinRequired), + openOnRemote: opts.openOnRemote, }, { cancelled: () => { - if (path) { - window.location.href = path; + if (opts.path) { + window.location.href = opts.path; } }, closed: () => dispose(), diff --git a/packages/frontend/src/scripts/select-file.ts b/packages/frontend/src/scripts/select-file.ts index 9aa38178b2..b037aa8acc 100644 --- a/packages/frontend/src/scripts/select-file.ts +++ b/packages/frontend/src/scripts/select-file.ts @@ -80,7 +80,7 @@ export function chooseFileFromUrl(): Promise<Misskey.entities.DriveFile> { }); } -function select(src: any, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { +function select(src: HTMLElement | EventTarget | null, label: string | null, multiple: boolean): Promise<Misskey.entities.DriveFile[]> { return new Promise((res, rej) => { const keepOriginal = ref(defaultStore.state.keepOriginalUploading); @@ -107,10 +107,10 @@ function select(src: any, label: string | null, multiple: boolean): Promise<Miss }); } -export function selectFile(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile> { +export function selectFile(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile> { return select(src, label, false).then(files => files[0]); } -export function selectFiles(src: any, label: string | null = null): Promise<Misskey.entities.DriveFile[]> { +export function selectFiles(src: HTMLElement | EventTarget | null, label: string | null = null): Promise<Misskey.entities.DriveFile[]> { return select(src, label, true); } diff --git a/packages/frontend/src/scripts/shuffle.ts b/packages/frontend/src/scripts/shuffle.ts index fed16bc71c..1f6ef1928c 100644 --- a/packages/frontend/src/scripts/shuffle.ts +++ b/packages/frontend/src/scripts/shuffle.ts @@ -6,8 +6,9 @@ /** * é…列をシャッフル (ç ´å£Šçš„) */ -export function shuffle<T extends any[]>(array: T): T { - let currentIndex = array.length, randomIndex; +export function shuffle<T extends unknown[]>(array: T): T { + let currentIndex = array.length; + let randomIndex: number; // While there remain elements to shuffle. while (currentIndex !== 0) { diff --git a/packages/frontend/src/scripts/theme.ts b/packages/frontend/src/scripts/theme.ts index bd3cddde67..8242e7d2e4 100644 --- a/packages/frontend/src/scripts/theme.ts +++ b/packages/frontend/src/scripts/theme.ts @@ -123,7 +123,7 @@ export function applyTheme(theme: Theme, persist = true) { for (const [k, v] of Object.entries(props)) { if (k.startsWith('font')) continue; - document.documentElement.style.setProperty(`--${k}`, v.toString()); + document.documentElement.style.setProperty(`--MI_THEME-${k}`, v.toString()); } document.documentElement.style.setProperty('color-scheme', colorScheme); diff --git a/packages/frontend/src/scripts/upload.ts b/packages/frontend/src/scripts/upload.ts index 22dce609c6..713573a377 100644 --- a/packages/frontend/src/scripts/upload.ts +++ b/packages/frontend/src/scripts/upload.ts @@ -32,13 +32,13 @@ const mimeTypeMap = { export function uploadFile( file: File, - folder?: any, + folder?: string | Misskey.entities.DriveFolder, name?: string, keepOriginal: boolean = defaultStore.state.keepOriginalUploading, ): Promise<Misskey.entities.DriveFile> { if ($i == null) throw new Error('Not logged in'); - if (folder && typeof folder === 'object') folder = folder.id; + const _folder = typeof folder === 'string' ? folder : folder?.id; if (file.size > instance.maxFileSize) { alert({ @@ -89,11 +89,11 @@ export function uploadFile( } const formData = new FormData(); - formData.append('i', $i.token); + formData.append('i', $i!.token); formData.append('force', 'true'); formData.append('file', resizedImage ?? file); formData.append('name', ctx.name); - if (folder) formData.append('folderId', folder); + if (_folder) formData.append('folderId', _folder); const xhr = new XMLHttpRequest(); xhr.open('POST', apiUrl + '/drive/files/create', true); diff --git a/packages/frontend/src/server-context.ts b/packages/frontend/src/server-context.ts new file mode 100644 index 0000000000..aa44a10290 --- /dev/null +++ b/packages/frontend/src/server-context.ts @@ -0,0 +1,23 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ +import * as Misskey from 'misskey-js'; +import { $i } from '@/account.js'; + +const providedContextEl = document.getElementById('misskey_clientCtx'); + +export type ServerContext = { + clip?: Misskey.entities.Clip; + note?: Misskey.entities.Note; + user?: Misskey.entities.UserLite; +} | null; + +export const serverContext: ServerContext = (providedContextEl && providedContextEl.textContent) ? JSON.parse(providedContextEl.textContent) : null; + +export function getServerContext<K extends keyof NonNullable<ServerContext>>(entity: K): Required<Pick<NonNullable<ServerContext>, K>> | null { + // contextã¯éžãƒã‚°ã‚¤ãƒ³çŠ¶æ…‹ã®æƒ…å ±ã—ã‹ãªã„ãŸã‚ãƒã‚°ã‚¤ãƒ³æ™‚ã¯åˆ©ç”¨ã§ããªã„ + if ($i) return null; + + return serverContext ? (serverContext[entity] ?? null) : null; +} diff --git a/packages/frontend/src/store.ts b/packages/frontend/src/store.ts index bbd9873ad8..c34e0bbf48 100644 --- a/packages/frontend/src/store.ts +++ b/packages/frontend/src/store.ts @@ -8,11 +8,13 @@ import * as Misskey from 'misskey-js'; import { hemisphere } from '@@/js/intl-const.js'; import lightTheme from '@@/themes/l-cherry.json5'; import darkTheme from '@@/themes/d-ice.json5'; -import { miLocalStorage } from './local-storage.js'; import { searchEngineMap } from './scripts/search-engine-map.js'; import type { SoundType } from '@/scripts/sound.js'; +import { DEFAULT_DEVICE_KIND, type DeviceKind } from '@/scripts/device-kind.js'; +import { miLocalStorage } from '@/local-storage.js'; import { defaultFollowingFeedState } from '@/scripts/following-feed-utils.js'; import { Storage } from '@/pizzax.js'; +import type { Ast } from '@syuilo/aiscript'; interface PostFormAction { title: string, @@ -80,6 +82,10 @@ export const defaultStore = markRaw(new Storage('base', { global: false, }, }, + abusesTutorial: { + where: 'account', + default: false, + }, keepCw: { where: 'account', default: true, @@ -249,7 +255,7 @@ export const defaultStore = markRaw(new Storage('base', { overridedDeviceKind: { where: 'device', - default: null as null | 'smartphone' | 'tablet' | 'desktop', + default: null as DeviceKind | null, }, serverDisconnectedBehavior: { where: 'device', @@ -269,7 +275,7 @@ export const defaultStore = markRaw(new Storage('base', { }, animatedMfm: { where: 'device', - default: false, + default: !window.matchMedia('(prefers-reduced-motion)').matches, }, advancedMfm: { where: 'device', @@ -317,11 +323,11 @@ export const defaultStore = markRaw(new Storage('base', { }, useBlurEffectForModal: { where: 'device', - default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環å‚ç…§ã™ã‚‹ã®ã§device-kind.tsã¯å‚ç…§ã§ããªã„ + default: DEFAULT_DEVICE_KIND === 'desktop', }, useBlurEffect: { where: 'device', - default: !/mobile|iphone|android/.test(navigator.userAgent.toLowerCase()), // 循環å‚ç…§ã™ã‚‹ã®ã§device-kind.tsã¯å‚ç…§ã§ããªã„ + default: DEFAULT_DEVICE_KIND === 'desktop', }, showFixedPostForm: { where: 'device', @@ -551,6 +557,10 @@ export const defaultStore = markRaw(new Storage('base', { where: 'device', default: 'app' as 'app' | 'appWithShift' | 'native', }, + skipNoteRender: { + where: 'device', + default: true, + }, sound_masterVolume: { where: 'device', @@ -595,7 +605,7 @@ export type Plugin = { token: string; src: string | null; version: string; - ast: any[]; + ast: Ast.Node[]; author?: string; description?: string; permissions?: string[]; @@ -633,13 +643,13 @@ export class ColdDeviceStorage { } public static getAll(): Partial<typeof this.default> { - return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce((acc, key) => { + return (Object.keys(this.default) as (keyof typeof this.default)[]).reduce<Partial<typeof this.default>>((acc, key) => { const value = localStorage.getItem(PREFIX + key); if (value != null) { acc[key] = JSON.parse(value); } return acc; - }, {} as any); + }, {}); } public static set<T extends keyof typeof ColdDeviceStorage.default>(key: T, value: typeof ColdDeviceStorage.default[T]): void { @@ -684,7 +694,7 @@ export class ColdDeviceStorage { get: () => { return valueRef.value; }, - set: (value: unknown) => { + set: (value: typeof ColdDeviceStorage.default[K]) => { const val = value; ColdDeviceStorage.set(key, val); }, diff --git a/packages/frontend/src/style.scss b/packages/frontend/src/style.scss index d990a706b3..2e8060fda7 100644 --- a/packages/frontend/src/style.scss +++ b/packages/frontend/src/style.scss @@ -15,54 +15,52 @@ */ :root { - --radius-xs: 5px; - --radius-sm: 5px; - --radius: 5px; - --radius-md: 5px; - --radius-lg: 5px; - --radius-xl: 5px; - --radius-ellipse: 5px; - --radius-full: 5px; + --MI-radius-xs: 5px; + --MI-radius-sm: 5px; + --MI-radius: 5px; + --MI-radius-md: 5px; + --MI-radius-lg: 5px; + --MI-radius-xl: 5px; + --MI-radius-ellipse: 5px; + --MI-radius-full: 5px; - --marginFull: 16px; - --marginHalf: 10px; + --MI-marginFull: 16px; + --MI-marginHalf: 10px; - --margin: var(--marginFull); + --MI-margin: var(--MI-marginFull); // switch dynamically - --minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px))); - --minBottomSpacing: var(--minBottomSpacingMobile); + --MI-minBottomSpacingMobile: calc(72px + max(12px, env(safe-area-inset-bottom, 0px))); + --MI-minBottomSpacing: var(--MI-minBottomSpacingMobile); - //--ad: rgb(255 169 0 / 10%); + --MI-avatar: 48px; + --MI-thread-width: 2px; @media (max-width: 500px) { - --margin: var(--marginHalf); + --MI-margin: var(--MI-marginHalf); } - - --avatar: 48px; - --thread-width: 2px; } html.radius-misskey { - --radius-xs: 4px; - --radius-sm: 8px; - --radius: 12px; - --radius-md: 16px; - --radius-lg: 24px; - --radius-xl: 32px; - --radius-ellipse: 999px; - --radius-full: 100%; + --MI-radius-xs: 4px; + --MI-radius-sm: 8px; + --MI-radius: 12px; + --MI-radius-md: 16px; + --MI-radius-lg: 24px; + --MI-radius-xl: 32px; + --MI-radius-ellipse: 999px; + --MI-radius-full: 100%; } ::selection { - color: var(--fgOnAccent); - background-color: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background-color: var(--MI_THEME-accent); } html { - background-color: var(--bg); - color: var(--fg); - accent-color: var(--accent); + background-color: var(--MI_THEME-bg); + color: var(--MI_THEME-fg); + accent-color: var(--MI_THEME-accent); overflow: auto; overflow-wrap: break-word; font-family: 'sharkey-theme-font-face', 'Lexend', 'Hiragino Maru Gothic Pro', "BIZ UDGothic", Roboto, HelveticaNeue, Arial, sans-serif; @@ -73,7 +71,7 @@ html { -webkit-text-size-adjust: 100%; &, * { - scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent; scrollbar-width: thin; &::-webkit-scrollbar { @@ -86,14 +84,14 @@ html { } &::-webkit-scrollbar-thumb { - background: var(--scrollbarHandle); + background: var(--MI_THEME-scrollbarHandle); &:hover { - background: var(--scrollbarHandleHover); + background: var(--MI_THEME-scrollbarHandleHover); } &:active { - background: var(--accent); + background: var(--MI_THEME-accent); } } } @@ -155,15 +153,15 @@ textarea, input { } optgroup, option { - background: var(--panel); - color: var(--fg); + background: var(--MI_THEME-panel); + color: var(--MI_THEME-fg); } hr { - margin: var(--margin) 0 var(--margin) 0; + margin: var(--MI-margin) 0 var(--MI-margin) 0; border: none; height: 1px; - background: var(--divider); + background: var(--MI_THEME-divider); } rt { @@ -171,7 +169,7 @@ rt { } :focus-visible { - outline: var(--focus) solid 2px; + outline: var(--MI_THEME-focus) solid 2px; outline-offset: -2px; &:hover { @@ -198,20 +196,20 @@ rt { display: inline-block; width: 1em; height: 1em; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); background: currentColor; } ._indicateCounter { display: inline-flex; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: 700; - background: var(--indicator); + background: var(--MI_THEME-indicator); height: 1.5em; min-width: 1em; align-items: center; justify-content: center; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); padding: 0.3em 0.5em; } @@ -239,13 +237,13 @@ rt { left: 0; width: 100%; height: 100%; - background: var(--modalBg); - -webkit-backdrop-filter: var(--modalBgFilter); - backdrop-filter: var(--modalBgFilter); + background: var(--MI_THEME-modalBg); + -webkit-backdrop-filter: var(--MI-modalBgFilter); + backdrop-filter: var(--MI-modalBgFilter); } ._shadow { - box-shadow: 0px 4px 32px var(--shadow) !important; + box-shadow: 0px 4px 32px var(--MI_THEME-shadow) !important; } ._button { @@ -278,40 +276,40 @@ rt { ._buttonPrimary { @extend ._button; - color: var(--fgOnAccent); - background: var(--accent); + color: var(--MI_THEME-fgOnAccent); + background: var(--MI_THEME-accent); &:not(:disabled):hover { - background: hsl(from var(--accent) h s calc(l + 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l + 5)); } &:not(:disabled):active { - background: hsl(from var(--accent) h s calc(l - 5)); + background: hsl(from var(--MI_THEME-accent) h s calc(l - 5)); } } ._buttonGradate { @extend ._buttonPrimary; - color: var(--fgOnAccent); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); &:not(:disabled):hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } &:not(:disabled):active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } ._help { - color: var(--accent); + color: var(--MI_THEME-accent); cursor: help; } ._textButton { @extend ._button; - color: var(--accent); + color: var(--MI_THEME-accent); &:focus-visible { outline-offset: 2px; @@ -323,13 +321,13 @@ rt { } ._panel { - background: color-mix(in srgb, var(--panel) 65%, transparent); - border-radius: var(--radius); + background: color-mix(in srgb, var(--MI_THEME-panel) 65%, transparent); + border-radius: var(--MI-radius); overflow: clip; } ._margin { - margin: var(--margin) 0; + margin: var(--MI-margin) 0; } ._gaps_m { @@ -347,7 +345,7 @@ rt { ._gaps { display: flex; flex-direction: column; - gap: var(--margin); + gap: var(--MI-margin); } ._buttons { @@ -369,24 +367,24 @@ rt { padding: 10px; box-sizing: border-box; text-align: center; - border: solid 0.5px var(--divider); - border-radius: var(--radius); + border: solid 0.5px var(--MI_THEME-divider); + border-radius: var(--MI-radius); &:active { - border-color: var(--accent); + border-color: var(--MI_THEME-accent); } } ._popup { - background: var(--popup); - border-radius: var(--radius); + background: var(--MI_THEME-popup); + border-radius: var(--MI-radius); contain: content; } ._acrylic { - background: var(--acrylicPanel); - -webkit-backdrop-filter: var(--blur, blur(15px)); - backdrop-filter: var(--blur, blur(15px)); + background: var(--MI_THEME-acrylicPanel); + -webkit-backdrop-filter: var(--MI-blur, blur(15px)); + backdrop-filter: var(--MI-blur, blur(15px)); } ._formLinksGrid { @@ -399,9 +397,9 @@ rt { margin-left: 0.7em; font-size: 65%; padding: 2px 3px; - color: var(--accent); - border: solid 1px var(--accent); - border-radius: var(--radius-xs); + color: var(--MI_THEME-accent); + border: solid 1px var(--MI_THEME-accent); + border-radius: var(--MI-radius-xs); vertical-align: top; } @@ -409,8 +407,8 @@ rt { margin-left: 0.7em; font-size: 65%; padding: 2px 3px; - color: var(--warn); - border: solid 1px var(--warn); + color: var(--MI_THEME-warn); + border: solid 1px var(--MI_THEME-warn); border-radius: 4px; vertical-align: top; } @@ -451,12 +449,12 @@ rt { vertical-align: bottom; height: 128px; margin-bottom: 16px; - border-radius: var(--radius-md); + border-radius: var(--MI-radius-md); } } ._link { - color: var(--link); + color: var(--MI_THEME-link); } ._caption { @@ -480,14 +478,14 @@ rt { box-shadow: 0 6px 16px #0007, 0 0 1px 1px #693410, inset 0 0 2px 1px #ce8a5c; border-radius: 10px; - --bg: #F1E8DC; - --fg: #693410; + --MI_THEME-bg: #F1E8DC; + --MI_THEME-fg: #693410; } html[data-color-scheme=dark] ._woodenFrame { - --bg: #1d0c02; - --fg: #F1E8DC; - --panel: #192320; + --MI_THEME-bg: #1d0c02; + --MI_THEME-fg: #F1E8DC; + --MI_THEME-panel: #192320; } ._woodenFrameH { @@ -498,10 +496,10 @@ html[data-color-scheme=dark] ._woodenFrame { ._woodenFrameInner { padding: 8px; margin-top: 8px; - background: var(--bg); + background: var(--MI_THEME-bg); box-shadow: 0 0 2px 1px #ce8a5c, inset 0 0 1px 1px #693410; border-radius: 6px; - color: var(--fg); + color: var(--MI_THEME-fg); &:first-child { margin-top: 0; @@ -516,7 +514,11 @@ html[data-color-scheme=dark] ._woodenFrame { transform: scale(0.9); } -@keyframes global-blink { +._blink { + animation: blink 1s infinite; +} + +@keyframes blink { 0% { opacity: 1; transform: scale(1); } 30% { opacity: 1; transform: scale(1); } 90% { opacity: 0; transform: scale(0.5); } @@ -529,20 +531,17 @@ html[data-color-scheme=dark] ._woodenFrame { 10%, 20% { - transform: scale3d(0.9, 0.9, 0.9) rotate3d(0, 0, 1, -3deg); + transform: scale3d(0.91, 0.91, 0.91) rotate3d(0, 0, 1, -2deg); } 30%, - 50%, - 70%, - 90% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, 3deg); + 70% { + transform: scale3d(1.09, 1.09, 1.09) rotate3d(0, 0, 1, 2deg); } - 40%, - 60%, - 80% { - transform: scale3d(1.1, 1.1, 1.1) rotate3d(0, 0, 1, -3deg); + 50%, + 90% { + transform: scale3d(1.09, 1.09, 1.09) rotate3d(0, 0, 1, -2deg); } to { diff --git a/packages/frontend/src/types/post-form.ts b/packages/frontend/src/types/post-form.ts new file mode 100644 index 0000000000..94958b6623 --- /dev/null +++ b/packages/frontend/src/types/post-form.ts @@ -0,0 +1,24 @@ +/* + * SPDX-FileCopyrightText: syuilo and misskey-project + * SPDX-License-Identifier: AGPL-3.0-only + */ + +import * as Misskey from 'misskey-js'; + +export interface PostFormProps { + reply?: Misskey.entities.Note; + renote?: Misskey.entities.Note; + channel?: Misskey.entities.Channel; // TODO + mention?: Misskey.entities.User; + specified?: Misskey.entities.UserDetailed; + initialText?: string; + initialCw?: string; + initialVisibility?: (typeof Misskey.noteVisibilities)[number]; + initialFiles?: Misskey.entities.DriveFile[]; + initialLocalOnly?: boolean; + initialVisibleUsers?: Misskey.entities.UserDetailed[]; + initialNote?: Misskey.entities.Note & { + isSchedule?: boolean, + }; + instant?: boolean; +} diff --git a/packages/frontend/src/ui/_common_/announcements.vue b/packages/frontend/src/ui/_common_/announcements.vue index 374bc20b54..d153dc8726 100644 --- a/packages/frontend/src/ui/_common_/announcements.vue +++ b/packages/frontend/src/ui/_common_/announcements.vue @@ -13,9 +13,9 @@ SPDX-License-Identifier: AGPL-3.0-only > <span :class="$style.icon"> <i v-if="announcement.icon === 'info'" class="ti ti-info-circle"></i> - <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--warn);"></i> - <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--error);"></i> - <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--success);"></i> + <i v-else-if="announcement.icon === 'warning'" class="ti ti-alert-triangle" style="color: var(--MI_THEME-warn);"></i> + <i v-else-if="announcement.icon === 'error'" class="ti ti-circle-x" style="color: var(--MI_THEME-error);"></i> + <i v-else-if="announcement.icon === 'success'" class="ti ti-check" style="color: var(--MI_THEME-success);"></i> </span> <span :class="$style.title">{{ announcement.title }}</span> <span :class="$style.body">{{ announcement.text }}</span> @@ -30,7 +30,7 @@ import { $i } from '@/account.js'; <style lang="scss" module> .root { font-size: 15px; - background: var(--panel); + background: var(--MI_THEME-panel); } .item { @@ -44,8 +44,8 @@ import { $i } from '@/account.js'; height: var(--height); overflow: clip; contain: strict; - background: var(--accent); - color: var(--fgOnAccent); + background: var(--MI_THEME-accent); + color: var(--MI_THEME-fgOnAccent); @container (max-width: 1000px) { display: block; diff --git a/packages/frontend/src/ui/_common_/common.vue b/packages/frontend/src/ui/_common_/common.vue index a8ff2a4c8d..7e29a5eeff 100644 --- a/packages/frontend/src/ui/_common_/common.vue +++ b/packages/frontend/src/ui/_common_/common.vue @@ -129,26 +129,26 @@ function getPointerEvents() { .notifications { position: fixed; z-index: 3900000; - padding: 0 var(--margin); + padding: 0 var(--MI-margin); display: flex; &.notificationsPosition_leftTop { - top: var(--margin); + top: var(--MI-margin); left: 0; } &.notificationsPosition_rightTop { - top: var(--margin); + top: var(--MI-margin); right: 0; } &.notificationsPosition_leftBottom { - bottom: calc(var(--minBottomSpacing) + var(--margin)); + bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin)); left: 0; } &.notificationsPosition_rightBottom { - bottom: calc(var(--minBottomSpacing) + var(--margin)); + bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin)); right: 0; } @@ -246,8 +246,8 @@ function getPointerEvents() { height: 18px; box-sizing: border-box; border: solid 2px transparent; - border-top-color: var(--accent); - border-left-color: var(--accent); + border-top-color: var(--MI_THEME-accent); + border-left-color: var(--MI_THEME-accent); border-radius: 50%; animation: progress-spinner 400ms linear infinite; } diff --git a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue index f3244b5697..aa72de6089 100644 --- a/packages/frontend/src/ui/_common_/navbar-for-mobile.vue +++ b/packages/frontend/src/ui/_common_/navbar-for-mobile.vue @@ -19,7 +19,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="item === '-'" :class="$style.divider"></div> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" class="_button" :class="[$style.item, { [$style.active]: navbarItemDef[item].active }]" :activeClass="$style.active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"> + <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink"> <span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span> <i v-else class="_indicatorCircle"></i> </span> @@ -31,7 +31,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkA> <button :class="$style.item" class="_button" @click="more"> <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> + <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span> </button> <MkA :class="$style.item" :activeClass="$style.active" to="/settings"> <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span> @@ -82,7 +82,7 @@ function more() { <style lang="scss" module> .root { - --nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5); + --nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5); display: flex; flex-direction: column; @@ -94,8 +94,8 @@ function more() { z-index: 1; padding: 20px 0; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .banner { @@ -135,8 +135,8 @@ function more() { bottom: 0; padding: 20px 0; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .post { @@ -144,7 +144,7 @@ function more() { display: block; width: 100%; height: 40px; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: bold; text-align: left; @@ -159,13 +159,13 @@ function more() { left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-ellipse); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } &:hover, &.active { &::before { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } } } @@ -209,7 +209,7 @@ function more() { .divider { margin: 16px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .item { @@ -223,15 +223,15 @@ function more() { width: 100%; text-align: left; box-sizing: border-box; - color: var(--navFg); + color: var(--MI_THEME-navFg); &:hover { text-decoration: none; - color: var(--navHoverFg); + color: var(--MI_THEME-navHoverFg); } &.active { - color: var(--navActive); + color: var(--MI_THEME-navActive); } &:hover, &.active { @@ -246,8 +246,8 @@ function more() { left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: var(--accentedBg); + border-radius: var(--MI-radius-ellipse); + background: var(--MI_THEME-accentedBg); } } } @@ -262,9 +262,8 @@ function more() { position: absolute; top: 0; left: 20px; - color: var(--navIndicator); + color: var(--MI_THEME-navIndicator); font-size: 8px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; diff --git a/packages/frontend/src/ui/_common_/navbar.vue b/packages/frontend/src/ui/_common_/navbar.vue index 17690df412..5cc0e52f77 100644 --- a/packages/frontend/src/ui/_common_/navbar.vue +++ b/packages/frontend/src/ui/_common_/navbar.vue @@ -29,7 +29,7 @@ SPDX-License-Identifier: AGPL-3.0-only v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}" > <i class="ti-fw" :class="[$style.itemIcon, navbarItemDef[item].icon]"></i><span :class="$style.itemText">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator"> + <span v-if="navbarItemDef[item].indicated" :class="$style.itemIndicator" class="_blink"> <span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span> <i v-else class="_indicatorCircle"></i> </span> @@ -41,7 +41,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkA> <button class="_button" :class="$style.item" @click="more"> <i :class="$style.itemIcon" class="ti ti-grid-dots ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.more }}</span> - <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator"><i class="_indicatorCircle"></i></span> + <span v-if="otherMenuItemIndicated" :class="$style.itemIndicator" class="_blink"><i class="_indicatorCircle"></i></span> </button> <MkA v-tooltip.noDelay.right="i18n.ts.settings" :class="$style.item" :activeClass="$style.active" to="/settings"> <i :class="$style.itemIcon" class="ti ti-settings ti-fw"></i><span :class="$style.itemText">{{ i18n.ts.settings }}</span> @@ -56,6 +56,21 @@ SPDX-License-Identifier: AGPL-3.0-only </button> </div> </div> + <button v-if="!forceIconOnly" class="_button" :class="$style.toggleButton" @click="toggleIconOnly"> + <!-- + <svg viewBox="0 0 16 48" :class="$style.toggleButtonShape"> + <g transform="matrix(0.333333,0,0,0.222222,0.000895785,13.3333)"> + <path d="M23.935,-24C37.223,-24 47.995,-7.842 47.995,12.09C47.995,34.077 47.995,62.07 47.995,84.034C47.995,93.573 45.469,102.721 40.972,109.466C36.475,116.211 30.377,120 24.018,120L23.997,120C10.743,120 -0.003,136.118 -0.003,156C-0.003,156 -0.003,156 -0.003,156L-0.003,-60L-0.003,-59.901C-0.003,-50.379 2.519,-41.248 7.007,-34.515C11.496,-27.782 17.584,-24 23.931,-24C23.932,-24 23.934,-24 23.935,-24Z" style="fill:var(--MI_THEME-navBg);"/> + </g> + </svg> + --> + <svg viewBox="0 0 16 64" :class="$style.toggleButtonShape"> + <g transform="matrix(0.333333,0,0,0.222222,0.000895785,21.3333)"> + <path d="M47.488,7.995C47.79,10.11 47.943,12.266 47.943,14.429C47.997,26.989 47.997,84 47.997,84C47.997,84 44.018,118.246 23.997,133.5C-0.374,152.07 -0.003,192 -0.003,192L-0.003,-96C-0.003,-96 0.151,-56.216 23.997,-37.5C40.861,-24.265 46.043,-1.243 47.488,7.995Z" style="fill:var(--MI_THEME-navBg);"/> + </g> + </svg> + <i :class="'ti ' + `ti-chevron-${ iconOnly ? 'right' : 'left' }`" style="font-size: 12px; margin-left: -8px;"></i> + </button> </div> </template> @@ -80,9 +95,11 @@ const otherMenuItemIndicated = computed(() => { return false; }); -const calcViewState = () => { - iconOnly.value = (window.innerWidth <= 1279) || (defaultStore.state.menuDisplay === 'sideIcon'); -}; +const forceIconOnly = window.innerWidth <= 1279; + +function calcViewState() { + iconOnly.value = forceIconOnly || (defaultStore.state.menuDisplay === 'sideIcon'); +} calcViewState(); @@ -92,6 +109,10 @@ watch(defaultStore.reactiveState.menuDisplay, () => { calcViewState(); }); +function toggleIconOnly() { + defaultStore.set('menuDisplay', iconOnly.value ? 'sideFull' : 'sideIcon'); +} + function openAccountMenu(ev: MouseEvent) { openAccountMenu_({ withExtraOperation: true, @@ -111,7 +132,7 @@ function more(ev: MouseEvent) { .root { --nav-width: 250px; --nav-icon-only-width: 80px; - --nav-bg-transparent: color(from var(--navBg) srgb r g b / 0.5); + --nav-bg-transparent: color(from var(--MI_THEME-navBg) srgb r g b / 0.5); flex: 0 0 var(--nav-width); width: var(--nav-width); @@ -129,10 +150,42 @@ function more(ev: MouseEvent) { overflow: auto; overflow-x: clip; overscroll-behavior: contain; - background: var(--navBg); + background: var(--MI_THEME-navBg); contain: strict; display: flex; flex-direction: column; + direction: rtl; // スクãƒãƒ¼ãƒ«ãƒãƒ¼ã‚’å·¦ã«è¡¨ç¤ºã—ãŸã„ãŸã‚ +} + +.top { + direction: ltr; +} + +.middle { + direction: ltr; +} + +.bottom { + direction: ltr; +} + +.toggleButton { + position: fixed; + bottom: 20px; + left: var(--nav-width); + z-index: 1001; + width: 16px; + height: 64px; + box-sizing: border-box; +} + +.toggleButtonShape { + position: absolute; + z-index: -1; + top: 0; + left: 0; + width: 16px; + height: 64px; } .root:not(.iconOnly) { @@ -146,8 +199,8 @@ function more(ev: MouseEvent) { z-index: 1; padding: 20px 0; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .banner { @@ -172,7 +225,7 @@ function more(ev: MouseEvent) { outline: none; > .instanceIcon { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: 2px; } } @@ -196,8 +249,8 @@ function more(ev: MouseEvent) { bottom: 0; padding-top: 20px; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .post { @@ -205,7 +258,7 @@ function more(ev: MouseEvent) { display: block; width: 100%; height: 40px; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); font-weight: bold; text-align: left; @@ -220,22 +273,22 @@ function more(ev: MouseEvent) { left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-ellipse); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } &:focus-visible { outline: none; &::before { - outline: 2px solid var(--fgOnAccent); + outline: 2px solid var(--MI_THEME-fgOnAccent); outline-offset: -4px; } } &:hover, &.active { &::before { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } } } @@ -265,7 +318,7 @@ function more(ev: MouseEvent) { outline: none; > .avatar { - box-shadow: 0 0 0 4px var(--focus); + box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } } @@ -291,7 +344,7 @@ function more(ev: MouseEvent) { .divider { margin: 16px 16px; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .item { @@ -305,28 +358,28 @@ function more(ev: MouseEvent) { width: 100%; text-align: left; box-sizing: border-box; - color: var(--navFg); + color: var(--MI_THEME-navFg); &:hover { text-decoration: none; - color: var(--navHoverFg); + color: var(--MI_THEME-navHoverFg); } &.active { - color: var(--navActive); + color: var(--MI_THEME-navActive); } &:focus-visible { outline: none; &::before { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: -2px; } } &:hover, &.active, &:focus { - color: var(--accent); + color: var(--MI_THEME-accent); &::before { content: ""; @@ -339,8 +392,8 @@ function more(ev: MouseEvent) { left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: var(--accentedBg); + border-radius: var(--MI-radius-ellipse); + background: var(--MI_THEME-accentedBg); } } } @@ -355,9 +408,8 @@ function more(ev: MouseEvent) { position: absolute; top: 0; left: 20px; - color: var(--navIndicator); + color: var(--MI_THEME-navIndicator); font-size: 8px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; @@ -371,6 +423,10 @@ function more(ev: MouseEvent) { position: relative; font-size: 0.9em; } + + .toggleButton { + left: var(--nav-width); + } } .root.iconOnly { @@ -387,8 +443,8 @@ function more(ev: MouseEvent) { z-index: 1; padding: 20px 0; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .instance { @@ -400,7 +456,7 @@ function more(ev: MouseEvent) { outline: none; > .instanceIcon { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: 2px; } } @@ -417,8 +473,8 @@ function more(ev: MouseEvent) { bottom: 0; padding-top: 20px; background: var(--nav-bg-transparent); - -webkit-backdrop-filter: var(--blur, blur(8px)); - backdrop-filter: var(--blur, blur(8px)); + -webkit-backdrop-filter: var(--MI-blur, blur(8px)); + backdrop-filter: var(--MI-blur, blur(8px)); } .post { @@ -439,29 +495,29 @@ function more(ev: MouseEvent) { margin: auto; width: 52px; aspect-ratio: 1/1; - border-radius: var(--radius-full); - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); + border-radius: var(--MI-radius-full); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); } &:focus-visible { outline: none; &::before { - outline: 2px solid var(--fgOnAccent); + outline: 2px solid var(--MI_THEME-fgOnAccent); outline-offset: -4px; } } &:hover, &.active { &::before { - background: var(--accentLighten); + background: var(--MI_THEME-accentLighten); } } } .postIcon { position: relative; - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); } .postText { @@ -479,7 +535,7 @@ function more(ev: MouseEvent) { outline: none; > .avatar { - box-shadow: 0 0 0 4px var(--focus); + box-shadow: 0 0 0 4px var(--MI_THEME-focus); } } } @@ -501,7 +557,7 @@ function more(ev: MouseEvent) { .divider { margin: 8px auto; width: calc(100% - 32px); - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } .item { @@ -515,14 +571,14 @@ function more(ev: MouseEvent) { outline: none; &::before { - outline: 2px solid var(--focus); + outline: 2px solid var(--MI_THEME-focus); outline-offset: -2px; } } &:hover, &.active, &:focus { text-decoration: none; - color: var(--accent); + color: var(--MI_THEME-accent); &::before { content: ""; @@ -535,8 +591,8 @@ function more(ev: MouseEvent) { left: 0; right: 0; bottom: 0; - border-radius: var(--radius-ellipse); - background: var(--accentedBg); + border-radius: var(--MI-radius-ellipse); + background: var(--MI_THEME-accentedBg); } > .icon, @@ -560,9 +616,8 @@ function more(ev: MouseEvent) { position: absolute; top: 6px; left: 24px; - color: var(--navIndicator); + color: var(--MI_THEME-navIndicator); font-size: 8px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; @@ -572,5 +627,9 @@ function more(ev: MouseEvent) { font-size: 10px; } } + + .toggleButton { + left: var(--nav-icon-only-width); + } } </style> diff --git a/packages/frontend/src/ui/_common_/notification.vue b/packages/frontend/src/ui/_common_/notification.vue index 29ae04387a..2d527c1a65 100644 --- a/packages/frontend/src/ui/_common_/notification.vue +++ b/packages/frontend/src/ui/_common_/notification.vue @@ -22,7 +22,7 @@ defineProps<{ <style lang="scss" module> .root { box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); overflow: clip; contain: content; } diff --git a/packages/frontend/src/ui/_common_/statusbar-rss.vue b/packages/frontend/src/ui/_common_/statusbar-rss.vue index 550fc39b00..da8fa8bb21 100644 --- a/packages/frontend/src/ui/_common_/statusbar-rss.vue +++ b/packages/frontend/src/ui/_common_/statusbar-rss.vue @@ -48,7 +48,7 @@ const fetching = ref(true); const key = ref(0); const tick = () => { - window.fetch(`/api/fetch-rss?url=${props.url}`, {}).then(res => { + window.fetch(`/api/fetch-rss?url=${encodeURIComponent(props.url)}`, {}).then(res => { res.json().then((feed: Misskey.entities.FetchRssResponse) => { if (props.shuffle) { shuffle(feed.items); diff --git a/packages/frontend/src/ui/_common_/statusbars.vue b/packages/frontend/src/ui/_common_/statusbars.vue index 690366307b..5f9a938017 100644 --- a/packages/frontend/src/ui/_common_/statusbars.vue +++ b/packages/frontend/src/ui/_common_/statusbars.vue @@ -32,7 +32,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') <style lang="scss" module> .root { font-size: 15px; - background: var(--panel); + background: var(--MI_THEME-panel); } .item { @@ -81,7 +81,7 @@ const XUserList = defineAsyncComponent(() => import('./statusbar-user-list.vue') .name { padding: 0 var(--nameMargin); font-weight: bold; - color: var(--accent); + color: var(--MI_THEME-accent); &:empty { display: none; diff --git a/packages/frontend/src/ui/_common_/stream-indicator.vue b/packages/frontend/src/ui/_common_/stream-indicator.vue index ad93b7e61c..cc62a28b14 100644 --- a/packages/frontend/src/ui/_common_/stream-indicator.vue +++ b/packages/frontend/src/ui/_common_/stream-indicator.vue @@ -48,8 +48,8 @@ onUnmounted(() => { .root { position: fixed; z-index: v-bind(zIndex); - bottom: calc(var(--minBottomSpacing) + var(--margin)); - right: var(--margin); + bottom: calc(var(--MI-minBottomSpacing) + var(--MI-margin)); + right: var(--MI-margin); margin: 0; padding: 12px; font-size: 0.9em; diff --git a/packages/frontend/src/ui/_common_/upload.vue b/packages/frontend/src/ui/_common_/upload.vue index 96030c7897..12de579d90 100644 --- a/packages/frontend/src/ui/_common_/upload.vue +++ b/packages/frontend/src/ui/_common_/upload.vue @@ -40,7 +40,7 @@ const zIndex = os.claimZIndex('high'); padding: 16px 20px; pointer-events: none; box-shadow: 0 4px 16px rgba(0, 0, 0, 0.3); - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } .mk-uploader:empty { display: none; @@ -116,7 +116,7 @@ const zIndex = os.claimZIndex('high'); display: block; background: transparent; border: none; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); overflow: hidden; grid-column: 2/3; grid-row: 2/3; @@ -125,10 +125,10 @@ const zIndex = os.claimZIndex('high'); height: 8px; } .mk-uploader > ol > li > progress::-webkit-progress-value { - background: var(--accent); + background: var(--MI_THEME-accent); } .mk-uploader > ol > li > progress::-webkit-progress-bar { - //background: var(--accentAlpha01); + //background: var(--MI_THEME-accentAlpha01); background: transparent; } </style> diff --git a/packages/frontend/src/ui/classic.header.vue b/packages/frontend/src/ui/classic.header.vue index c03afd6cd6..f4633314ae 100644 --- a/packages/frontend/src/ui/classic.header.vue +++ b/packages/frontend/src/ui/classic.header.vue @@ -17,7 +17,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="item === '-'" class="divider"></div> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime v-tooltip="navbarItemDef[item].title" class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i class="ti-fw" :class="navbarItemDef[item].icon"></i> - <span v-if="navbarItemDef[item].indicated" class="indicator"><i class="_indicatorCircle"></i></span> + <span v-if="navbarItemDef[item].indicated" class="indicator _blink"><i class="_indicatorCircle"></i></span> </component> </template> <div class="divider"></div> @@ -26,7 +26,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkA> <button v-click-anime class="item _button" @click="more"> <i class="ti ti-dots ti-fw"></i> - <span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span> + <span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span> </button> </div> <div class="right"> @@ -104,7 +104,7 @@ onMounted(() => { z-index: 1000; width: 100%; height: $height; - background-color: var(--bg); + background-color: var(--MI_THEME-bg); > .body { max-width: 1380px; @@ -140,18 +140,17 @@ onMounted(() => { position: absolute; top: 0; left: 0; - color: var(--navIndicator); + color: var(--MI_THEME-navIndicator); font-size: 8px; - animation: global-blink 1s infinite; } &:hover { text-decoration: none; - color: var(--navHoverFg); + color: var(--MI_THEME-navHoverFg); } &.active { - color: var(--navActive); + color: var(--MI_THEME-navActive); } } @@ -159,7 +158,7 @@ onMounted(() => { display: inline-block; height: 16px; margin: 0 10px; - border-right: solid 0.5px var(--divider); + border-right: solid 0.5px var(--MI_THEME-divider); } > .instance { diff --git a/packages/frontend/src/ui/classic.sidebar.vue b/packages/frontend/src/ui/classic.sidebar.vue index 96cc24c9b9..f17027bcde 100644 --- a/packages/frontend/src/ui/classic.sidebar.vue +++ b/packages/frontend/src/ui/classic.sidebar.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only <div v-if="item === '-'" class="divider"></div> <component :is="navbarItemDef[item].to ? 'MkA' : 'button'" v-else-if="navbarItemDef[item] && (navbarItemDef[item].show !== false)" v-click-anime class="item _button" :class="item" activeClass="active" :to="navbarItemDef[item].to" v-on="navbarItemDef[item].action ? { click: navbarItemDef[item].action } : {}"> <i class="ti-fw" :class="navbarItemDef[item].icon"></i><span class="text">{{ navbarItemDef[item].title }}</span> - <span v-if="navbarItemDef[item].indicated" class="indicator"> + <span v-if="navbarItemDef[item].indicated" class="indicator _blink"> <span v-if="navbarItemDef[item].indicateValue" class="_indicateCounter itemIndicateValueIcon">{{ navbarItemDef[item].indicateValue }}</span> <i v-else class="_indicatorCircle"></i> </span> @@ -33,7 +33,7 @@ SPDX-License-Identifier: AGPL-3.0-only </MkA> <button v-click-anime class="item _button" @click="more"> <i class="ti ti-dots ti-fw"></i><span class="text">{{ i18n.ts.more }}</span> - <span v-if="otherNavItemIndicated" class="indicator"><i class="_indicatorCircle"></i></span> + <span v-if="otherNavItemIndicated" class="indicator _blink"><i class="_indicatorCircle"></i></span> </button> <MkA v-click-anime class="item" activeClass="active" to="/settings" :behavior="settingsWindowed ? 'window' : null"> <i class="ti ti-settings ti-fw"></i><span class="text">{{ i18n.ts.settings }}</span> @@ -159,7 +159,7 @@ watch(defaultStore.reactiveState.menuDisplay, () => { > .divider { margin: 10px 0; - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > .post { @@ -224,9 +224,8 @@ watch(defaultStore.reactiveState.menuDisplay, () => { position: absolute; top: 0; left: 0; - color: var(--navIndicator); + color: var(--MI_THEME-navIndicator); font-size: 8px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; @@ -237,11 +236,11 @@ watch(defaultStore.reactiveState.menuDisplay, () => { &:hover { text-decoration: none; - color: var(--navHoverFg); + color: var(--MI_THEME-navHoverFg); } &.active { - color: var(--navActive); + color: var(--MI_THEME-navActive); } } } diff --git a/packages/frontend/src/ui/classic.vue b/packages/frontend/src/ui/classic.vue index 31bb1ddc14..ded945dda1 100644 --- a/packages/frontend/src/ui/classic.vue +++ b/packages/frontend/src/ui/classic.vue @@ -12,7 +12,7 @@ SPDX-License-Identifier: AGPL-3.0-only <XSidebar/> </div> <div v-else-if="!pageMetadata?.needWideArea" ref="widgetsLeft" class="widgets left"> - <XWidgets place="left" :marginTop="'var(--margin)'" @mounted="attachSticky(widgetsLeft)"/> + <XWidgets place="left" :marginTop="'var(--MI-margin)'" @mounted="attachSticky(widgetsLeft)"/> </div> <main class="main" @contextmenu.stop="onContextmenu"> @@ -22,7 +22,7 @@ SPDX-License-Identifier: AGPL-3.0-only </main> <div v-if="isDesktop && !pageMetadata?.needWideArea" ref="widgetsRight" class="widgets right"> - <XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--margin)'" @mounted="attachSticky(widgetsRight)"/> + <XWidgets :place="showMenuOnTop ? 'right' : null" :marginTop="showMenuOnTop ? '0' : 'var(--MI-margin)'" @mounted="attachSticky(widgetsRight)"/> </div> </div> @@ -216,8 +216,8 @@ onMounted(() => { box-sizing: border-box; &.wallpaper { - background: var(--wallpaperOverlay); - //backdrop-filter: var(--blur, blur(4px)); + background: var(--MI_THEME-wallpaperOverlay); + //backdrop-filter: var(--MI-blur, blur(4px)); } > .columns { @@ -249,17 +249,16 @@ onMounted(() => { min-width: 0; width: 750px; margin: 0 16px 0 0; - border-left: solid 1px var(--divider); - border-right: solid 1px var(--divider); + border-left: solid 1px var(--MI_THEME-divider); + border-right: solid 1px var(--MI_THEME-divider); border-radius: 0; overflow: clip; - --margin: 12px; + --MI-margin: 12px; } > .widgets { position: sticky; top: 0; - width: 300px; height: 100%; padding-top: 16px; box-sizing: border-box; @@ -281,13 +280,13 @@ onMounted(() => { &.withGlobalHeader { > .main { margin-top: 0; - border: solid 1px var(--divider); - border-radius: var(--radius); - --stickyTop: var(--globalHeaderHeight); + border: solid 1px var(--MI_THEME-divider); + border-radius: var(--MI-radius); + --MI-stickyTop: var(--globalHeaderHeight); } > .widgets { - --stickyTop: var(--globalHeaderHeight); + --MI-stickyTop: var(--globalHeaderHeight); margin-top: 0; } } @@ -296,7 +295,7 @@ onMounted(() => { margin: 0; > .sidebar { - border-right: solid 0.5px var(--divider); + border-right: solid 0.5px var(--MI_THEME-divider); } > .main { @@ -318,10 +317,10 @@ onMounted(() => { right: 0; z-index: 1001; height: 100dvh; - padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)); + padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)); box-sizing: border-box; overflow: auto; - background: var(--bg); + background: var(--MI_THEME-bg); } > .ivnzpscs { diff --git a/packages/frontend/src/ui/deck.vue b/packages/frontend/src/ui/deck.vue index b42a63e090..c0ea833546 100644 --- a/packages/frontend/src/ui/deck.vue +++ b/packages/frontend/src/ui/deck.vue @@ -55,11 +55,11 @@ SPDX-License-Identifier: AGPL-3.0-only </div> <div v-if="isMobile" :class="$style.nav"> - <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button> + <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button> <button :class="$style.navButton" class="_button" @click="mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button> <button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"> <i :class="$style.navButtonIcon" class="ti ti-bell"></i> - <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"> + <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink"> <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span> </span> </button> @@ -102,6 +102,7 @@ import { v4 as uuid } from 'uuid'; import XCommon from './_common_/common.vue'; import { deckStore, columnTypes, addColumn as addColumnToStore, loadDeck, getProfiles, deleteProfile as deleteProfile_ } from './deck/deck-store.js'; import type { ColumnType } from './deck/deck-store.js'; +import type { MenuItem } from '@/types/menu.js'; import XSidebar from '@/ui/_common_/navbar.vue'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import MkButton from '@/components/MkButton.vue'; @@ -124,7 +125,6 @@ import XDirectColumn from '@/ui/deck/direct-column.vue'; import XRoleTimelineColumn from '@/ui/deck/role-timeline-column.vue'; import XFollowingColumn from '@/ui/deck/following-column.vue'; import { mainRouter } from '@/router/main.js'; -import type { MenuItem } from '@/types/menu.js'; const XStatusBars = defineAsyncComponent(() => import('@/ui/_common_/statusbars.vue')); const XAnnouncements = defineAsyncComponent(() => import('@/ui/_common_/announcements.vue')); @@ -312,7 +312,7 @@ body { .root { $nav-hide-threshold: 650px; // TODO: ã©ã“ã‹ã«é›†ç´„ã—ãŸã„ - --margin: var(--marginHalf); + --MI-margin: var(--MI-marginHalf); --columnGap: 6px; @@ -339,7 +339,7 @@ body { overflow-x: auto; overflow-y: clip; overscroll-behavior: contain; - background: var(--deckBg); + background: var(--MI_THEME-deckBg); &.center { > .section:first-of-type { @@ -421,7 +421,7 @@ body { contain: strict; overflow: auto; overscroll-behavior: contain; - background: var(--navBg); + background: var(--MI_THEME-navBg); } .nav { @@ -435,10 +435,10 @@ body { grid-gap: 8px; width: 100%; box-sizing: border-box; - -webkit-backdrop-filter: var(--blur, blur(32px)); - backdrop-filter: var(--blur, blur(32px)); - background-color: var(--header); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(32px)); + backdrop-filter: var(--MI-blur, blur(32px)); + background-color: var(--MI_THEME-header); + border-top: solid 0.5px var(--MI_THEME-divider); } .navButton { @@ -448,33 +448,33 @@ body { width: 100%; max-width: 60px; margin: auto; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); background: transparent; - color: var(--fg); + color: var(--MI_THEME-fg); &:hover { - color: var(--accent); + color: var(--MI_THEME-accent); } &:active { - color: var(--accent); - background: hsl(from var(--panel) h s calc(l - 2)); + color: var(--MI_THEME-accent); + background: hsl(from var(--MI_THEME-panel) h s calc(l - 2)); } } .postButton { composes: navButton; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - color: var(--fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); &:hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); - color: var(--fgOnAccent); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); + color: var(--MI_THEME-fgOnAccent); } &:active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); - color: var(--fgOnAccent); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); + color: var(--MI_THEME-fgOnAccent); } } @@ -487,9 +487,8 @@ body { position: absolute; top: 0; left: 0; - color: var(--indicator); + color: var(--MI_THEME-indicator); font-size: 16px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; diff --git a/packages/frontend/src/ui/deck/column.vue b/packages/frontend/src/ui/deck/column.vue index 5ed3aa754f..2eb232096e 100644 --- a/packages/frontend/src/ui/deck/column.vue +++ b/packages/frontend/src/ui/deck/column.vue @@ -21,7 +21,7 @@ SPDX-License-Identifier: AGPL-3.0-only > <svg viewBox="0 0 256 128" :class="$style.tabShape"> <g transform="matrix(6.2431,0,0,6.2431,-677.417,-29.3839)"> - <path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--deckBg);"/> + <path d="M149.512,4.707L108.507,4.707C116.252,4.719 118.758,14.958 118.758,14.958C118.758,14.958 121.381,25.283 129.009,25.209L149.512,25.209L149.512,4.707Z" style="fill:var(--MI_THEME-deckBg);"/> </g> </svg> <div :class="$style.color"></div> @@ -287,7 +287,7 @@ function onDrop(ev) { height: 100%; overflow: clip; contain: strict; - border-radius: var(--radius); + border-radius: var(--MI-radius); &.draghover { &::after { @@ -299,7 +299,7 @@ function onDrop(ev) { left: 0; width: 100%; height: 100%; - background: var(--focus); + background: var(--MI_THEME-focus); } } @@ -313,7 +313,7 @@ function onDrop(ev) { left: 0; width: 100%; height: 100%; - background: var(--focus); + background: var(--MI_THEME-focus); opacity: 0.5; } } @@ -331,19 +331,19 @@ function onDrop(ev) { } &.naked { - background: var(--acrylicBg) !important; - -webkit-backdrop-filter: var(--blur, blur(10px)); - backdrop-filter: var(--blur, blur(10px)); + background: var(--MI_THEME-acrylicBg) !important; + -webkit-backdrop-filter: var(--MI-blur, blur(10px)); + backdrop-filter: var(--MI-blur, blur(10px)); > .header { background: transparent; box-shadow: none; - color: var(--fg); + color: var(--MI_THEME-fg); } > .body { background: transparent !important; - scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent; &::-webkit-scrollbar-track { background: transparent; @@ -352,12 +352,12 @@ function onDrop(ev) { } &.paged { - background: var(--bg) !important; + background: var(--MI_THEME-bg) !important; > .body { - background: var(--bg) !important; + background: var(--MI_THEME-bg) !important; overflow-y: scroll !important; - scrollbar-color: var(--scrollbarHandle) transparent; + scrollbar-color: var(--MI_THEME-scrollbarHandle) transparent; &::-webkit-scrollbar-track { background: inherit; @@ -374,9 +374,9 @@ function onDrop(ev) { height: var(--deckColumnHeaderHeight); padding: 0 16px 0 30px; font-size: 0.9em; - color: var(--panelHeaderFg); - background: var(--panelHeaderBg); - box-shadow: 0 1px 0 0 var(--panelHeaderDivider); + color: var(--MI_THEME-panelHeaderFg); + background: var(--MI_THEME-panelHeaderBg); + box-shadow: 0 1px 0 0 var(--MI_THEME-panelHeaderDivider); cursor: pointer; user-select: none; } @@ -387,8 +387,8 @@ function onDrop(ev) { left: 12px; width: 3px; height: calc(100% - 24px); - background: var(--accent); - border-radius: var(--radius-ellipse); + background: var(--MI_THEME-accent); + border-radius: var(--MI-radius-ellipse); } .tabShape { @@ -441,11 +441,11 @@ function onDrop(ev) { overscroll-behavior-y: contain; box-sizing: border-box; container-type: size; - background-color: var(--bg); - scrollbar-color: var(--scrollbarHandle) var(--panel); + background-color: var(--MI_THEME-bg); + scrollbar-color: var(--MI_THEME-scrollbarHandle) var(--MI_THEME-panel); &::-webkit-scrollbar-track { - background: var(--panel); + background: var(--MI_THEME-panel); } } </style> diff --git a/packages/frontend/src/ui/deck/deck-store.ts b/packages/frontend/src/ui/deck/deck-store.ts index ccc9af8d12..91859b46d7 100644 --- a/packages/frontend/src/ui/deck/deck-store.ts +++ b/packages/frontend/src/ui/deck/deck-store.ts @@ -50,6 +50,7 @@ export type Column = { tl?: BasicTimelineType; withRenotes?: boolean; withReplies?: boolean; + withSensitive?: boolean; onlyFiles?: boolean; soundSetting: SoundStore; }; diff --git a/packages/frontend/src/ui/deck/tl-column.vue b/packages/frontend/src/ui/deck/tl-column.vue index 8315f7fca5..8f5553ccae 100644 --- a/packages/frontend/src/ui/deck/tl-column.vue +++ b/packages/frontend/src/ui/deck/tl-column.vue @@ -24,6 +24,7 @@ SPDX-License-Identifier: AGPL-3.0-only :src="column.tl" :withRenotes="withRenotes" :withReplies="withReplies" + :withSensitive="withSensitive" :onlyFiles="onlyFiles" @note="onNote" /> @@ -54,6 +55,7 @@ const timeline = shallowRef<InstanceType<typeof MkTimeline>>(); const soundSetting = ref<SoundStore>(props.column.soundSetting ?? { type: null, volume: 1 }); const withRenotes = ref(props.column.withRenotes ?? true); const withReplies = ref(props.column.withReplies ?? false); +const withSensitive = ref(props.column.withSensitive ?? true); const onlyFiles = ref(props.column.onlyFiles ?? false); watch(withRenotes, v => { @@ -68,6 +70,12 @@ watch(withReplies, v => { }); }); +watch(withSensitive, v => { + updateColumn(props.column.id, { + withSensitive: v, + }); +}); + watch(onlyFiles, v => { updateColumn(props.column.id, { onlyFiles: v, @@ -146,6 +154,10 @@ const menu = computed<MenuItem[]>(() => { text: i18n.ts.fileAttachedOnly, ref: onlyFiles, disabled: hasWithReplies(props.column.tl) ? withReplies : false, + }, { + type: 'switch', + text: i18n.ts.withSensitive, + ref: withSensitive, }); return menuItems; diff --git a/packages/frontend/src/ui/deck/widgets-column.vue b/packages/frontend/src/ui/deck/widgets-column.vue index 9995996771..a0e62c8264 100644 --- a/packages/frontend/src/ui/deck/widgets-column.vue +++ b/packages/frontend/src/ui/deck/widgets-column.vue @@ -57,10 +57,10 @@ const menu = [{ <style lang="scss" module> .root { - --margin: 8px; - --panelBorder: none; + --MI-margin: 8px; + --MI_THEME-panelBorder: none; - padding: 0 var(--margin); + padding: 0 var(--MI-margin); } .intro { diff --git a/packages/frontend/src/ui/universal.vue b/packages/frontend/src/ui/universal.vue index 2fdaca775b..c552b65318 100644 --- a/packages/frontend/src/ui/universal.vue +++ b/packages/frontend/src/ui/universal.vue @@ -25,11 +25,11 @@ SPDX-License-Identifier: AGPL-3.0-only <button v-if="(!isDesktop || pageMetadata?.needWideArea) && !isMobile" :class="$style.widgetButton" class="_button" @click="widgetsShowing = true"><i class="ti ti-apps"></i></button> <div v-if="isMobile" ref="navFooter" :class="$style.nav"> - <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator"><i class="_indicatorCircle"></i></span></button> + <button :class="$style.navButton" class="_button" @click="drawerMenuShowing = true"><i :class="$style.navButtonIcon" class="ti ti-menu-2"></i><span v-if="menuIndicated" :class="$style.navButtonIndicator" class="_blink"><i class="_indicatorCircle"></i></span></button> <button :class="$style.navButton" class="_button" @click="isRoot ? top() : mainRouter.push('/')"><i :class="$style.navButtonIcon" class="ti ti-home"></i></button> <button :class="$style.navButton" class="_button" @click="mainRouter.push('/my/notifications')"> <i :class="$style.navButtonIcon" class="ti ti-bell"></i> - <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator"> + <span v-if="$i?.hasUnreadNotification" :class="$style.navButtonIndicator" class="_blink"> <span class="_indicateCounter" :class="$style.itemIndicateValueIcon">{{ $i.unreadNotificationsCount > 99 ? '99+' : $i.unreadNotificationsCount }}</span> </span> </button> @@ -96,9 +96,11 @@ SPDX-License-Identifier: AGPL-3.0-only <script lang="ts" setup> import { defineAsyncComponent, provide, onMounted, computed, ref, watch, shallowRef, Ref } from 'vue'; +import { instanceName } from '@@/js/config.js'; +import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js'; +import { isLink } from '@@/js/is-link.js'; import XCommon from './_common_/common.vue'; import type MkStickyContainer from '@/components/global/MkStickyContainer.vue'; -import { instanceName } from '@@/js/config.js'; import XDrawerMenu from '@/ui/_common_/navbar-for-mobile.vue'; import * as os from '@/os.js'; import { defaultStore } from '@/store.js'; @@ -108,10 +110,8 @@ import { $i } from '@/account.js'; import { PageMetadata, provideMetadataReceiver, provideReactiveMetadata } from '@/scripts/page-metadata.js'; import { deviceKind } from '@/scripts/device-kind.js'; import { miLocalStorage } from '@/local-storage.js'; -import { CURRENT_STICKY_BOTTOM } from '@@/js/const.js'; import { useScrollPositionManager } from '@/nirax.js'; import { mainRouter } from '@/router/main.js'; -import { isLink } from '@@/js/is-link.js'; const XWidgets = defineAsyncComponent(() => import('./universal.widgets.vue')); const XSidebar = defineAsyncComponent(() => import('@/ui/_common_/navbar.vue')); @@ -225,12 +225,12 @@ provide<Ref<number>>(CURRENT_STICKY_BOTTOM, navFooterHeight); watch(navFooter, () => { if (navFooter.value) { navFooterHeight.value = navFooter.value.offsetHeight; - document.body.style.setProperty('--stickyBottom', `${navFooterHeight.value}px`); - document.body.style.setProperty('--minBottomSpacing', 'var(--minBottomSpacingMobile)'); + document.body.style.setProperty('--MI-stickyBottom', `${navFooterHeight.value}px`); + document.body.style.setProperty('--MI-minBottomSpacing', 'var(--MI-minBottomSpacingMobile)'); } else { navFooterHeight.value = 0; - document.body.style.setProperty('--stickyBottom', '0px'); - document.body.style.setProperty('--minBottomSpacing', '0px'); + document.body.style.setProperty('--MI-stickyBottom', '0px'); + document.body.style.setProperty('--MI-minBottomSpacing', '0px'); } }, { immediate: true, @@ -318,7 +318,7 @@ $widgets-hide-threshold: 1090px; } .sidebar { - border-right: solid 0.5px var(--divider); + border-right: solid 0.5px var(--MI_THEME-divider); } .contents { @@ -328,7 +328,7 @@ $widgets-hide-threshold: 1090px; overflow: auto; overflow-y: scroll; overscroll-behavior: unset; - background: var(--bg); + background: var(--MI_THEME-bg); } .widgets { @@ -336,9 +336,9 @@ $widgets-hide-threshold: 1090px; height: 100%; box-sizing: border-box; overflow: auto; - padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)); - border-left: solid 0.5px var(--divider); - background: var(--bg); + padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)); + border-left: solid 0.5px var(--MI_THEME-divider); + background: var(--MI_THEME-bg); @media (max-width: $widgets-hide-threshold) { display: none; @@ -353,10 +353,10 @@ $widgets-hide-threshold: 1090px; right: 32px; width: 64px; height: 64px; - border-radius: var(--radius-full); + border-radius: var(--MI-radius-full); box-shadow: 0 3px 5px -1px rgba(0, 0, 0, 0.2), 0 6px 10px 0 rgba(0, 0, 0, 0.14), 0 1px 18px 0 rgba(0, 0, 0, 0.12); font-size: 22px; - background: var(--panel); + background: var(--MI_THEME-panel); } .widgetsDrawerBg { @@ -370,11 +370,11 @@ $widgets-hide-threshold: 1090px; z-index: 1001; width: 310px; height: 100dvh; - padding: var(--margin) var(--margin) calc(var(--margin) + env(safe-area-inset-bottom, 0px)) !important; + padding: var(--MI-margin) var(--MI-margin) calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)) !important; box-sizing: border-box; overflow: auto; overscroll-behavior: contain; - background: var(--bg); + background: var(--MI_THEME-bg); } .widgetsCloseButton { @@ -400,10 +400,10 @@ $widgets-hide-threshold: 1090px; grid-gap: 8px; width: 100%; box-sizing: border-box; - -webkit-backdrop-filter: var(--blur, blur(24px)); - backdrop-filter: var(--blur, blur(24px)); - background-color: var(--header); - border-top: solid 0.5px var(--divider); + -webkit-backdrop-filter: var(--MI-blur, blur(24px)); + backdrop-filter: var(--MI-blur, blur(24px)); + background-color: var(--MI_THEME-header); + border-top: solid 0.5px var(--MI_THEME-divider); } .navButton { @@ -413,34 +413,34 @@ $widgets-hide-threshold: 1090px; width: 100%; max-width: 60px; margin: auto; - border-radius: var(--radius-lg); + border-radius: var(--MI-radius-lg); background: transparent; - color: var(--fg); + color: var(--MI_THEME-fg); &:hover { - background: var(--panelHighlight); - color: var(--accent); + background: var(--MI_THEME-panelHighlight); + color: var(--MI_THEME-accent); } &:active { - background: hsl(from var(--panel) h s calc(l - 2)); - color: var(--accent); + background: hsl(from var(--MI_THEME-panel) h s calc(l - 2)); + color: var(--MI_THEME-accent); } } .postButton { composes: navButton; - background: linear-gradient(90deg, var(--buttonGradateA), var(--buttonGradateB)); - color: var(--fgOnAccent); + background: linear-gradient(90deg, var(--MI_THEME-buttonGradateA), var(--MI_THEME-buttonGradateB)); + color: var(--MI_THEME-fgOnAccent); &:hover { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); - color: var(--fgOnAccent); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); + color: var(--MI_THEME-fgOnAccent); } &:active { - background: linear-gradient(90deg, hsl(from var(--accent) h s calc(l + 5)), hsl(from var(--accent) h s calc(l + 5))); - color: var(--fgOnAccent); + color: var(--MI_THEME-fgOnAccent); + background: linear-gradient(90deg, hsl(from var(--MI_THEME-accent) h s calc(l + 5)), hsl(from var(--MI_THEME-accent) h s calc(l + 5))); } } @@ -453,9 +453,8 @@ $widgets-hide-threshold: 1090px; position: absolute; top: 0; left: 0; - color: var(--indicator); + color: var(--MI_THEME-indicator); font-size: 16px; - animation: global-blink 1s infinite; &:has(.itemIndicateValueIcon) { animation: none; @@ -478,7 +477,7 @@ $widgets-hide-threshold: 1090px; contain: strict; overflow: auto; overscroll-behavior: contain; - background: var(--navBg); + background: var(--MI_THEME-navBg); } .statusbars { @@ -488,6 +487,6 @@ $widgets-hide-threshold: 1090px; } .spacer { - height: calc(var(--minBottomSpacing)); + height: calc(var(--MI-minBottomSpacing)); } </style> diff --git a/packages/frontend/src/ui/visitor.vue b/packages/frontend/src/ui/visitor.vue index 510b2a4342..f048ac2124 100644 --- a/packages/frontend/src/ui/visitor.vue +++ b/packages/frontend/src/ui/visitor.vue @@ -189,7 +189,7 @@ defineExpose({ left: 0; width: 500px; height: 100vh; - background: var(--accent); + background: var(--MI_THEME-accent); z-index: 1; > .banner { @@ -218,7 +218,7 @@ defineExpose({ min-width: 0; > .header { - background: var(--panel); + background: var(--MI_THEME-panel); position: relative; z-index: 1; @@ -255,7 +255,7 @@ defineExpose({ left: 0; width: 240px; height: 100vh; - background: var(--panel); + background: var(--MI_THEME-panel); > .link { display: block; @@ -269,7 +269,7 @@ defineExpose({ > .divider { margin: 8px auto; width: calc(100% - 32px); - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > .action { @@ -281,10 +281,10 @@ defineExpose({ padding: 10px; box-sizing: border-box; text-align: center; - border-radius: var(--radius-ellipse); + border-radius: var(--MI-radius-ellipse); &._button { - background: var(--panel); + background: var(--MI_THEME-panel); } &:first-child { diff --git a/packages/frontend/src/ui/zen.vue b/packages/frontend/src/ui/zen.vue index ac13d7822f..757aa6669d 100644 --- a/packages/frontend/src/ui/zen.vue +++ b/packages/frontend/src/ui/zen.vue @@ -63,12 +63,12 @@ document.documentElement.style.overflowY = 'scroll'; } .rootWithBottom { - min-height: calc(100dvh - (60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px))); + min-height: calc(100dvh - (60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px))); box-sizing: border-box; } .bottom { - height: calc(60px + (var(--margin) * 2) + env(safe-area-inset-bottom, 0px)); + height: calc(60px + (var(--MI-margin) * 2) + env(safe-area-inset-bottom, 0px)); width: 100%; margin-top: auto; } @@ -80,10 +80,10 @@ document.documentElement.style.overflowY = 'scroll'; width: 100%; max-width: 60px; margin: auto; - border-radius: var(--radius-full); - background: var(--panel); - color: var(--fg); - right: var(--margin); - bottom: calc(var(--margin) + env(safe-area-inset-bottom, 0px)); + border-radius: var(--MI-radius-full); + background: var(--MI_THEME-panel); + color: var(--MI_THEME-fg); + right: var(--MI-margin); + bottom: calc(var(--MI-margin) + env(safe-area-inset-bottom, 0px)); } </style> diff --git a/packages/frontend/src/widgets/WidgetAiscript.vue b/packages/frontend/src/widgets/WidgetAiscript.vue index 8e29254819..946124058c 100644 --- a/packages/frontend/src/widgets/WidgetAiscript.vue +++ b/packages/frontend/src/widgets/WidgetAiscript.vue @@ -126,10 +126,10 @@ defineExpose<WidgetComponentExpose>({ max-width: 100%; min-width: 100%; padding: 16px; - color: var(--fg); + color: var(--MI_THEME-fg); background: transparent; border: none; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); border-radius: 0; box-sizing: border-box; font: inherit; @@ -145,7 +145,7 @@ defineExpose<WidgetComponentExpose>({ padding: 0 10px; height: 28px; outline: none; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); &:disabled { opacity: 0.7; @@ -154,7 +154,7 @@ defineExpose<WidgetComponentExpose>({ } > .logs { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); text-align: left; padding: 16px; diff --git a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue index bcfaaf00ab..c2bda85ac7 100644 --- a/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue +++ b/packages/frontend/src/widgets/WidgetBirthdayFollowings.vue @@ -115,7 +115,7 @@ defineExpose<WidgetComponentExpose>({ <style lang="scss" module> .bdayFRoot { overflow: hidden; - min-height: calc(calc(calc(50px * 3) - 8px) + calc(var(--margin) * 2)); + min-height: calc(calc(calc(50px * 3) - 8px) + calc(var(--MI-margin) * 2)); } .bdayFGrid { display: grid; @@ -123,7 +123,7 @@ defineExpose<WidgetComponentExpose>({ grid-template-rows: repeat(3, 42px); place-content: center; gap: 8px; - margin: var(--margin) auto; + margin: var(--MI-margin) auto; } .bdayFFallback { @@ -139,6 +139,6 @@ defineExpose<WidgetComponentExpose>({ width: auto; max-width: 90%; margin-bottom: 8px; - border-radius: var(--radius); + border-radius: var(--MI-radius); } </style> diff --git a/packages/frontend/src/widgets/WidgetCalendar.vue b/packages/frontend/src/widgets/WidgetCalendar.vue index e4ac1acfc7..a3fe93f025 100644 --- a/packages/frontend/src/widgets/WidgetCalendar.vue +++ b/packages/frontend/src/widgets/WidgetCalendar.vue @@ -207,8 +207,8 @@ defineExpose<WidgetComponentExpose>({ .meter { width: 100%; overflow: hidden; - background: var(--X11); - border-radius: var(--radius-sm); + background: var(--MI_THEME-X11); + border-radius: var(--MI-radius-sm); } .meterVal { diff --git a/packages/frontend/src/widgets/WidgetFederation.vue b/packages/frontend/src/widgets/WidgetFederation.vue index e91a77beab..260dd06c13 100644 --- a/packages/frontend/src/widgets/WidgetFederation.vue +++ b/packages/frontend/src/widgets/WidgetFederation.vue @@ -105,14 +105,14 @@ defineExpose<WidgetComponentExpose>({ display: flex; align-items: center; padding: 14px 16px; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); > img { display: block; width: ($bodyTitleHieght + $bodyInfoHieght); height: ($bodyTitleHieght + $bodyInfoHieght); object-fit: cover; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); margin-right: 8px; } @@ -120,7 +120,7 @@ defineExpose<WidgetComponentExpose>({ flex: 1; overflow: hidden; font-size: 0.9em; - color: var(--fg); + color: var(--MI_THEME-fg); padding-right: 8px; > .a { diff --git a/packages/frontend/src/widgets/WidgetInstanceInfo.vue b/packages/frontend/src/widgets/WidgetInstanceInfo.vue index 014cf01a5d..b99f9bdb2b 100644 --- a/packages/frontend/src/widgets/WidgetInstanceInfo.vue +++ b/packages/frontend/src/widgets/WidgetInstanceInfo.vue @@ -66,7 +66,7 @@ defineExpose<WidgetComponentExpose>({ display: inline-block; width: 60px; height: 60px; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); box-sizing: border-box; border: solid 3px #fff; } diff --git a/packages/frontend/src/widgets/WidgetJobQueue.vue b/packages/frontend/src/widgets/WidgetJobQueue.vue index edf6622a13..0ee6b863dc 100644 --- a/packages/frontend/src/widgets/WidgetJobQueue.vue +++ b/packages/frontend/src/widgets/WidgetJobQueue.vue @@ -173,14 +173,14 @@ defineExpose<WidgetComponentExpose>({ padding: 16px; &:not(:first-child) { - border-top: solid 0.5px var(--divider); + border-top: solid 0.5px var(--MI_THEME-divider); } > .label { display: flex; > .icon { - color: var(--warn); + color: var(--MI_THEME-warn); margin-left: auto; animation: warnBlink 1s infinite; } @@ -198,11 +198,11 @@ defineExpose<WidgetComponentExpose>({ > div:last-child { &.inc { - color: var(--warn); + color: var(--MI_THEME-warn); } &.dec { - color: var(--success); + color: var(--MI_THEME-success); } } } diff --git a/packages/frontend/src/widgets/WidgetMemo.vue b/packages/frontend/src/widgets/WidgetMemo.vue index ee89beb944..5addf1066d 100644 --- a/packages/frontend/src/widgets/WidgetMemo.vue +++ b/packages/frontend/src/widgets/WidgetMemo.vue @@ -84,10 +84,10 @@ defineExpose<WidgetComponentExpose>({ max-width: 100%; min-width: 100%; padding: 16px; - color: var(--fg); + color: var(--MI_THEME-fg); background: transparent; border: none; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); border-radius: 0; box-sizing: border-box; font: inherit; @@ -107,7 +107,7 @@ defineExpose<WidgetComponentExpose>({ padding: 0 10px; height: 28px; outline: none; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); &:disabled { opacity: 0.7; diff --git a/packages/frontend/src/widgets/WidgetOnlineUsers.vue b/packages/frontend/src/widgets/WidgetOnlineUsers.vue index d56ee96ac1..d8c4e259c8 100644 --- a/packages/frontend/src/widgets/WidgetOnlineUsers.vue +++ b/packages/frontend/src/widgets/WidgetOnlineUsers.vue @@ -72,6 +72,6 @@ defineExpose<WidgetComponentExpose>({ } .text { - color: var(--fgTransparentWeak); + color: var(--MI_THEME-fgTransparentWeak); } </style> diff --git a/packages/frontend/src/widgets/WidgetPhotos.vue b/packages/frontend/src/widgets/WidgetPhotos.vue index 86ece91de6..60a4770e40 100644 --- a/packages/frontend/src/widgets/WidgetPhotos.vue +++ b/packages/frontend/src/widgets/WidgetPhotos.vue @@ -68,10 +68,10 @@ const onDriveFileCreated = (file) => { } }; -const thumbnail = (image: any): string => { +const thumbnail = (image: Misskey.entities.DriveFile): string => { return defaultStore.state.disableShowingAnimatedImages ? getStaticImageUrl(image.url) - : image.thumbnailUrl; + : image.thumbnailUrl ?? image.url; }; misskeyApi('drive/stream', { @@ -102,7 +102,7 @@ defineExpose<WidgetComponentExpose>({ .img { border: solid 4px transparent; - border-radius: var(--radius-sm); + border-radius: var(--MI-radius-sm); } } @@ -121,7 +121,7 @@ defineExpose<WidgetComponentExpose>({ background-size: cover; background-clip: content-box; border: solid 2px transparent; - border-radius: var(--radius-xs); + border-radius: var(--MI-radius-xs); } } </style> diff --git a/packages/frontend/src/widgets/WidgetRss.vue b/packages/frontend/src/widgets/WidgetRss.vue index 511777a570..92dc6d148e 100644 --- a/packages/frontend/src/widgets/WidgetRss.vue +++ b/packages/frontend/src/widgets/WidgetRss.vue @@ -70,7 +70,7 @@ const items = computed(() => rawItems.value.slice(0, widgetProps.maxEntries)); const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', widgetProps.url); + url.searchParams.set('url', encodeURIComponent(widgetProps.url)); return url; }); const intervalClear = ref<(() => void) | undefined>(); @@ -113,7 +113,7 @@ defineExpose<WidgetComponentExpose>({ .item { display: block; padding: 8px 16px; - color: var(--fg); + color: var(--MI_THEME-fg); white-space: nowrap; text-overflow: ellipsis; overflow: hidden; diff --git a/packages/frontend/src/widgets/WidgetRssTicker.vue b/packages/frontend/src/widgets/WidgetRssTicker.vue index b393ecd74b..6957878572 100644 --- a/packages/frontend/src/widgets/WidgetRssTicker.vue +++ b/packages/frontend/src/widgets/WidgetRssTicker.vue @@ -99,7 +99,7 @@ const items = computed(() => { const fetching = ref(true); const fetchEndpoint = computed(() => { const url = new URL('/api/fetch-rss', base); - url.searchParams.set('url', widgetProps.url); + url.searchParams.set('url', encodeURIComponent(widgetProps.url)); return url; }); const intervalClear = ref<(() => void) | undefined>(); @@ -171,7 +171,7 @@ defineExpose<WidgetComponentExpose>({ display: inline-flex; align-items: center; vertical-align: bottom; - color: var(--fg); + color: var(--MI_THEME-fg); } .divider { @@ -179,6 +179,6 @@ defineExpose<WidgetComponentExpose>({ width: 0.5px; height: 16px; margin: 0 1em; - background: var(--divider); + background: var(--MI_THEME-divider); } </style> diff --git a/packages/frontend/src/widgets/WidgetSearch.vue b/packages/frontend/src/widgets/WidgetSearch.vue index 1a328be7ce..974536e880 100644 --- a/packages/frontend/src/widgets/WidgetSearch.vue +++ b/packages/frontend/src/widgets/WidgetSearch.vue @@ -176,6 +176,6 @@ defineExpose<WidgetComponentExpose>({ <style lang="scss" scoped> .skw-search { - border-radius: var(--radius-sm) !important; + border-radius: var(--MI-radius-sm) !important; } </style> diff --git a/packages/frontend/src/widgets/WidgetTrends.vue b/packages/frontend/src/widgets/WidgetTrends.vue index a41db513e8..47a4efc106 100644 --- a/packages/frontend/src/widgets/WidgetTrends.vue +++ b/packages/frontend/src/widgets/WidgetTrends.vue @@ -91,13 +91,13 @@ defineExpose<WidgetComponentExpose>({ display: flex; align-items: center; padding: 14px 16px; - border-bottom: solid 0.5px var(--divider); + border-bottom: solid 0.5px var(--MI_THEME-divider); > .tag { flex: 1; overflow: hidden; font-size: 0.9em; - color: var(--fg); + color: var(--MI_THEME-fg); > .a { display: block; diff --git a/packages/frontend/vite.config.ts b/packages/frontend/vite.config.ts index a51028c2c4..2ba63c010f 100644 --- a/packages/frontend/vite.config.ts +++ b/packages/frontend/vite.config.ts @@ -120,6 +120,11 @@ export function getConfig(): UserConfig { return shortId + '-' + toBase62(hash(id)).substring(0, 4); }, }, + preprocessorOptions: { + scss: { + api: 'modern-compiler', + }, + }, }, define: { diff --git a/packages/frontend/vite.replaceIcons.ts b/packages/frontend/vite.replaceIcons.ts index 71005fab31..9aa1e0c7b4 100644 --- a/packages/frontend/vite.replaceIcons.ts +++ b/packages/frontend/vite.replaceIcons.ts @@ -37,6 +37,7 @@ export function pluginReplaceIcons() { }, include: [ '**/pages/**', + '**/components/MkAuthConfirm.*', ], }), iconsReplace({ @@ -205,6 +206,7 @@ export function pluginReplaceIcons() { 'ti ti-confetti': 'ph-confetti ph-bold ph-lg', 'ti ti-cookie': 'ph-cookie ph-bold ph-lg', 'ti ti-copy': 'ph-copy ph-bold ph-lg', + 'ti ti-corner-up-right': 'ph-arrow-bend-up-right ph-bold ph-lg', 'ti ti-cpu': 'ph-cpu ph-bold ph-lg', 'ti ti-crop': 'ph-crop ph-bold ph-lg', 'ti ti-crown': 'ph-crown ph-bold ph-lg', @@ -237,6 +239,7 @@ export function pluginReplaceIcons() { 'ti ti-file-text': 'ph-file-text ph-bold ph-lg', 'ti ti-file-zip': 'ph-file-zip ph-bold ph-lg', 'ti ti-filter': 'ph-funnel ph-bold ph-lg', + 'ti ti-fingerprint': 'ph-fingerprint ph-bold ph-lg', 'ti ti-flare': 'ph-fire ph-bold ph-lg', 'ti ti-flask': 'ph-flask ph-bold ph-lg', 'ti ti-folder': 'ph-folder ph-bold ph-lg', @@ -255,6 +258,7 @@ export function pluginReplaceIcons() { 'ti ti-hourglass-empty': 'ph-hourglass ph-bold ph-lg', 'ti ti-id': 'ph-identification-card ph-bold ph-lg', 'ti ti-info-circle': 'ph-info ph-bold ph-lg', + 'ti ti-json': 'ph-brackets-curly ph-bold ph-lg', 'ti ti-key': 'ph-key ph-bold ph-lg', 'ti ti-language-hiragana': 'ph-translate ph-bold ph-lg', 'ti ti-leaf': 'ph-leaf ph-bold ph-lg', @@ -266,6 +270,7 @@ export function pluginReplaceIcons() { 'ti ti-lock': 'ph-lock ph-bold ph-lg', 'ti ti-lock-open': 'ph-lock-open ph-bold ph-lg', 'ti ti-lock-star': 'ph-shield-star ph-bold ph-lg', + 'ti ti-login-2': 'ph-sign-in ph-bold ph-lg', 'ti ti-mail': 'ph-envelope ph-bold ph-lg', 'ti ti-map-pin': 'ph-map-pin ph-bold ph-lg', 'ti ti-maximize': 'ph-frame-corners ph-bold ph-lg', @@ -273,6 +278,7 @@ export function pluginReplaceIcons() { 'ti ti-menu': 'ph-list ph-bold ph-lg', 'ti ti-menu-2': 'ph-list ph-bold ph-lg', 'ti ti-message': 'ph-envelope ph-bold ph-lg', + 'ti ti-message-2': 'ph-envelope ph-bold ph-lg', 'ti ti-message-exclamation': 'ph-exclamation ph-bold ph-lg', 'ti ti-message-off': 'ph-bell-slash ph-bold ph-lg', 'ti ti-message-x': 'ph-prohibit ph-bold ph-lg', @@ -334,7 +340,10 @@ export function pluginReplaceIcons() { 'ti ti-share': 'ph-share-network ph-bold ph-lg', 'ti ti-shield': 'ph-shield ph-bold ph-lg', 'ti ti-shield-lock': 'ph-shield ph-bold ph-lg', + 'ti ti-slash': 'ph-check-fat ph-bold ph-lg', 'ti ti-snowflake': 'ph-snowflake ph-bold ph-lg', + 'ti ti-sort-ascending-letters': 'ph-sort-ascending ph-bold ph-lg', + 'ti ti-sort-descending-letters': 'ph-sort-descending ph-bold ph-lg', 'ti ti-sparkles': 'ph-sparkle ph-bold ph-lg', 'ti ti-speakerphone': 'ph-megaphone ph-bold ph-lg', 'ti ti-stack-2': 'ph-stack ph-bold ph-lg', @@ -359,6 +368,7 @@ export function pluginReplaceIcons() { 'ti ti-user-search': 'ph-user-circle ph-bold ph-lg', 'ti ti-user-shield': 'ph-newspaper-clipping ph-bold ph-lg', 'ti ti-user-star': 'ph-user-focus ph-bold ph-lg', + 'ti ti-user-x': 'ph-prohibit ph-bold ph-lg', 'ti ti-users': 'ph-users ph-bold ph-lg', 'ti ti-video': 'ph-video ph-bold ph-lg', 'ti ti-volume': 'ph-speaker-high ph-bold ph-lg', diff --git a/packages/misskey-bubble-game/package.json b/packages/misskey-bubble-game/package.json index 2b280f17fc..a2484d5f6a 100644 --- a/packages/misskey-bubble-game/package.json +++ b/packages/misskey-bubble-game/package.json @@ -22,16 +22,16 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/matter-js": "0.19.6", + "@types/matter-js": "0.19.7", "@types/seedrandom": "3.0.8", - "@types/node": "20.11.5", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.1.0", "@typescript-eslint/parser": "7.1.0", - "nodemon": "3.0.2", + "nodemon": "3.1.7", "execa": "8.0.1", - "typescript": "5.3.3", - "esbuild": "0.19.11", - "glob": "10.3.10" + "typescript": "5.6.3", + "esbuild": "0.24.0", + "glob": "11.0.0" }, "files": [ "built" diff --git a/packages/misskey-js/etc/misskey-js.api.md b/packages/misskey-js/etc/misskey-js.api.md index 4a261028ed..0269cc2c86 100644 --- a/packages/misskey-js/etc/misskey-js.api.md +++ b/packages/misskey-js/etc/misskey-js.api.md @@ -4,7 +4,10 @@ ```ts +import type { AuthenticationResponseJSON } from '@simplewebauthn/types'; import { EventEmitter } from 'eventemitter3'; +import type { PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; +import _ReconnectingWebsocket from 'reconnecting-websocket'; // Warning: (ae-forgotten-export) The symbol "components" needs to be exported by the entry point index.d.ts // @@ -122,6 +125,9 @@ type AdminApproveUserRequest = operations['admin___approve-user']['requestBody'] type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json']; // @public (undocumented) +type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json']; + +// @public (undocumented) type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json']; // @public (undocumented) @@ -218,6 +224,9 @@ type AdminFederationRemoveAllFollowingRequest = operations['admin___federation__ type AdminFederationUpdateInstanceRequest = operations['admin___federation___update-instance']['requestBody']['content']['application/json']; // @public (undocumented) +type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json']; + +// @public (undocumented) type AdminGetIndexStatsResponse = operations['admin___get-index-stats']['responses']['200']['content']['application/json']; // @public (undocumented) @@ -395,6 +404,9 @@ type AdminUnsilenceUserRequest = operations['admin___unsilence-user']['requestBo type AdminUnsuspendUserRequest = operations['admin___unsuspend-user']['requestBody']['content']['application/json']; // @public (undocumented) +type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json']; + +// @public (undocumented) type AdminUpdateMetaRequest = operations['admin___update-meta']['requestBody']['content']['application/json']; // @public (undocumented) @@ -1190,13 +1202,25 @@ export type Endpoints = Overwrite<Endpoints_2, { req: SignupPendingRequest; res: SignupPendingResponse; }; - 'signin': { - req: SigninRequest; - res: SigninResponse; + 'signin-flow': { + req: SigninFlowRequest; + res: SigninFlowResponse; }; 'signin-with-passkey': { req: SigninWithPasskeyRequest; - res: SigninWithPasskeyResponse; + res: { + $switch: { + $cases: [ + [ + { + context: string; + }, + SigninWithPasskeyResponse + ] + ]; + $default: SigninWithPasskeyInitResponse; + }; + }; }; 'admin/roles/create': { req: Overwrite<AdminRolesCreateRequest, { @@ -1228,10 +1252,11 @@ declare namespace entities { SignupResponse, SignupPendingRequest, SignupPendingResponse, - SigninRequest, + SigninFlowRequest, + SigninFlowResponse, SigninWithPasskeyRequest, + SigninWithPasskeyInitResponse, SigninWithPasskeyResponse, - SigninResponse, PartialRolePolicyOverride, EmptyRequest, EmptyResponse, @@ -1265,6 +1290,7 @@ declare namespace entities { AdminAnnouncementsListResponse, AdminAnnouncementsUpdateRequest, AdminAvatarDecorationsCreateRequest, + AdminAvatarDecorationsCreateResponse, AdminAvatarDecorationsDeleteRequest, AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, @@ -1317,6 +1343,8 @@ declare namespace entities { AdminResetPasswordRequest, AdminResetPasswordResponse, AdminResolveAbuseUserReportRequest, + AdminForwardAbuseUserReportRequest, + AdminUpdateAbuseUserReportRequest, AdminSendEmailRequest, AdminServerInfoResponse, AdminShowModerationLogsRequest, @@ -1723,6 +1751,7 @@ declare namespace entities { FlashCreateRequest, FlashCreateResponse, FlashDeleteRequest, + FlashFeaturedRequest, FlashFeaturedResponse, FlashLikeRequest, FlashShowRequest, @@ -1974,6 +2003,9 @@ type FlashCreateResponse = operations['flash___create']['responses']['200']['con type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; // @public (undocumented) +type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json']; + +// @public (undocumented) type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; // @public (undocumented) @@ -2605,6 +2637,12 @@ type ModerationLog = { type: 'resolveAbuseReport'; info: ModerationLogPayloads['resolveAbuseReport']; } | { + type: 'forwardAbuseReport'; + info: ModerationLogPayloads['forwardAbuseReport']; +} | { + type: 'updateAbuseReportNote'; + info: ModerationLogPayloads['updateAbuseReportNote']; +} | { type: 'unsetUserAvatar'; info: ModerationLogPayloads['unsetUserAvatar']; } | { @@ -2643,7 +2681,7 @@ type ModerationLog = { }); // @public (undocumented) -export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"]; +export const moderationLogTypes: readonly ["updateServerSettings", "suspend", "approve", "unsuspend", "updateUserNote", "addCustomEmoji", "updateCustomEmoji", "deleteCustomEmoji", "assignRole", "unassignRole", "createRole", "updateRole", "deleteRole", "clearQueue", "promoteQueue", "deleteDriveFile", "deleteNote", "createGlobalAnnouncement", "createUserAnnouncement", "updateGlobalAnnouncement", "updateUserAnnouncement", "deleteGlobalAnnouncement", "deleteUserAnnouncement", "resetPassword", "suspendRemoteInstance", "unsuspendRemoteInstance", "updateRemoteInstanceNote", "markSensitiveDriveFile", "unmarkSensitiveDriveFile", "resolveAbuseReport", "forwardAbuseReport", "updateAbuseReportNote", "createInvitation", "createAd", "updateAd", "deleteAd", "createAvatarDecoration", "updateAvatarDecoration", "deleteAvatarDecoration", "unsetUserAvatar", "unsetUserBanner", "createSystemWebhook", "updateSystemWebhook", "deleteSystemWebhook", "createAbuseReportNotificationRecipient", "updateAbuseReportNotificationRecipient", "deleteAbuseReportNotificationRecipient", "deleteAccount", "deletePage", "deleteFlash", "deleteGalleryPost"]; // @public (undocumented) type MuteCreateRequest = operations['mute___create']['requestBody']['content']['application/json']; @@ -3142,29 +3180,48 @@ type ServerStatsLog = ServerStats[]; type Signin = components['schemas']['Signin']; // @public (undocumented) -type SigninRequest = { +type SigninFlowRequest = { username: string; - password: string; + password?: string; token?: string; + credential?: AuthenticationResponseJSON; + 'hcaptcha-response'?: string | null; + 'g-recaptcha-response'?: string | null; + 'turnstile-response'?: string | null; + 'm-captcha-response'?: string | null; }; // @public (undocumented) -type SigninResponse = { +type SigninFlowResponse = { + finished: true; id: User['id']; i: string; +} | { + finished: false; + next: 'captcha' | 'password' | 'totp'; +} | { + finished: false; + next: 'passkey'; + authRequest: PublicKeyCredentialRequestOptionsJSON; +}; + +// @public (undocumented) +type SigninWithPasskeyInitResponse = { + option: PublicKeyCredentialRequestOptionsJSON; + context: string; }; // @public (undocumented) type SigninWithPasskeyRequest = { - credential?: object; + credential?: AuthenticationResponseJSON; context?: string; }; // @public (undocumented) type SigninWithPasskeyResponse = { - option?: object; - context?: string; - signinResponse?: SigninResponse; + signinResponse: SigninFlowResponse & { + finished: true; + }; }; // @public (undocumented) @@ -3188,6 +3245,7 @@ type SignupRequest = { 'hcaptcha-response'?: string | null; 'g-recaptcha-response'?: string | null; 'turnstile-response'?: string | null; + 'm-captcha-response'?: string | null; }; // @public (undocumented) @@ -3206,7 +3264,7 @@ export class Stream extends EventEmitter<StreamEvents> implements IStream { constructor(origin: string, user: { token: string; } | null, options?: { - WebSocket?: WebSocket; + WebSocket?: _ReconnectingWebsocket.Options['WebSocket']; }); // (undocumented) close(): void; @@ -3468,7 +3526,7 @@ type UsersUpdateMemoRequest = operations['users___update-memo']['requestBody'][' // Warnings were encountered during analysis: // -// src/entities.ts:49:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts +// src/entities.ts:50:2 - (ae-forgotten-export) The symbol "ModerationLogPayloads" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:236:4 - (ae-forgotten-export) The symbol "ReversiUpdateKey" needs to be exported by the entry point index.d.ts // src/streaming.types.ts:246:4 - (ae-forgotten-export) The symbol "ReversiUpdateSettings" needs to be exported by the entry point index.d.ts diff --git a/packages/misskey-js/generator/package.json b/packages/misskey-js/generator/package.json index 1d31c73237..f64150882d 100644 --- a/packages/misskey-js/generator/package.json +++ b/packages/misskey-js/generator/package.json @@ -8,14 +8,14 @@ }, "devDependencies": { "@readme/openapi-parser": "2.6.0", - "@types/node": "20.9.1", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "openapi-types": "12.1.3", "openapi-typescript": "6.7.3", - "ts-case-convert": "2.0.7", + "ts-case-convert": "2.1.0", "tsx": "4.4.0", - "typescript": "5.6.2" + "typescript": "5.6.3" }, "files": [ "built" diff --git a/packages/misskey-js/package.json b/packages/misskey-js/package.json index 05cda46574..392f296bb0 100644 --- a/packages/misskey-js/package.json +++ b/packages/misskey-js/package.json @@ -1,7 +1,7 @@ { "type": "module", "name": "misskey-js", - "version": "2024.9.1", + "version": "2024.11.1-rc", "description": "Misskey SDK for JavaScript", "license": "MIT", "main": "./built/index.js", @@ -35,10 +35,10 @@ "directory": "packages/misskey-js" }, "devDependencies": { - "@microsoft/api-extractor": "7.47.9", - "@swc/jest": "0.2.36", - "@types/jest": "29.5.13", - "@types/node": "20.14.12", + "@microsoft/api-extractor": "7.47.11", + "@swc/jest": "0.2.37", + "@types/jest": "29.5.14", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.17.0", "@typescript-eslint/parser": "7.17.0", "jest": "29.7.0", @@ -47,16 +47,17 @@ "mock-socket": "9.3.1", "ncp": "2.0.0", "nodemon": "3.1.7", - "execa": "9.4.0", + "execa": "8.0.1", "tsd": "0.31.2", - "typescript": "5.6.2", - "esbuild": "0.23.1", + "typescript": "5.6.3", + "esbuild": "0.24.0", "glob": "11.0.0" }, "files": [ "built" ], "dependencies": { + "@simplewebauthn/types": "11.0.0", "eventemitter3": "5.0.1", "reconnecting-websocket": "4.4.0" } diff --git a/packages/misskey-js/src/api.types.ts b/packages/misskey-js/src/api.types.ts index 4c3f2e1578..838949f8e1 100644 --- a/packages/misskey-js/src/api.types.ts +++ b/packages/misskey-js/src/api.types.ts @@ -3,8 +3,9 @@ import { UserDetailed } from './autogen/models.js'; import { AdminRolesCreateRequest, AdminRolesCreateResponse, UsersShowRequest } from './autogen/entities.js'; import { PartialRolePolicyOverride, - SigninRequest, - SigninResponse, + SigninFlowRequest, + SigninFlowResponse, + SigninWithPasskeyInitResponse, SigninWithPasskeyRequest, SigninWithPasskeyResponse, SignupPendingRequest, @@ -80,14 +81,26 @@ export type Endpoints = Overwrite< res: SignupPendingResponse; }, // api.jsonã«ã¯è¼‰ã›ãªã„ã‚‚ã®ãªã®ã§ã“ã“ã§å®šç¾© - 'signin': { - req: SigninRequest; - res: SigninResponse; + 'signin-flow': { + req: SigninFlowRequest; + res: SigninFlowResponse; }, 'signin-with-passkey': { req: SigninWithPasskeyRequest; - res: SigninWithPasskeyResponse; - } + res: { + $switch: { + $cases: [ + [ + { + context: string; + }, + SigninWithPasskeyResponse, + ], + ]; + $default: SigninWithPasskeyInitResponse; + }, + }, + }, 'admin/roles/create': { req: Overwrite<AdminRolesCreateRequest, { policies: PartialRolePolicyOverride }>; res: AdminRolesCreateResponse; diff --git a/packages/misskey-js/src/autogen/apiClientJSDoc.ts b/packages/misskey-js/src/autogen/apiClientJSDoc.ts index 4b0e8173f8..ccb513b7f9 100644 --- a/packages/misskey-js/src/autogen/apiClientJSDoc.ts +++ b/packages/misskey-js/src/autogen/apiClientJSDoc.ts @@ -694,6 +694,28 @@ declare module '../api.js' { /** * No description provided. * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + request<E extends 'admin/forward-abuse-user-report', P extends Endpoints[E]['req']>( + endpoint: E, + params: P, + credential?: string | null, + ): Promise<SwitchCaseResponseType<E, P>>; + + /** + * No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + request<E extends 'admin/update-abuse-user-report', P extends Endpoints[E]['req']>( + endpoint: E, + params: P, + credential?: string | null, + ): Promise<SwitchCaseResponseType<E, P>>; + + /** + * No description provided. + * * **Credential required**: *Yes* / **Permission**: *write:admin:send-email* */ request<E extends 'admin/send-email', P extends Endpoints[E]['req']>( diff --git a/packages/misskey-js/src/autogen/endpoint.ts b/packages/misskey-js/src/autogen/endpoint.ts index 5caddb602b..66e7126460 100644 --- a/packages/misskey-js/src/autogen/endpoint.ts +++ b/packages/misskey-js/src/autogen/endpoint.ts @@ -31,6 +31,7 @@ import type { AdminAnnouncementsListResponse, AdminAnnouncementsUpdateRequest, AdminAvatarDecorationsCreateRequest, + AdminAvatarDecorationsCreateResponse, AdminAvatarDecorationsDeleteRequest, AdminAvatarDecorationsListRequest, AdminAvatarDecorationsListResponse, @@ -83,6 +84,8 @@ import type { AdminResetPasswordRequest, AdminResetPasswordResponse, AdminResolveAbuseUserReportRequest, + AdminForwardAbuseUserReportRequest, + AdminUpdateAbuseUserReportRequest, AdminSendEmailRequest, AdminServerInfoResponse, AdminShowModerationLogsRequest, @@ -489,6 +492,7 @@ import type { FlashCreateRequest, FlashCreateResponse, FlashDeleteRequest, + FlashFeaturedRequest, FlashFeaturedResponse, FlashLikeRequest, FlashShowRequest, @@ -619,7 +623,7 @@ export type Endpoints = { 'admin/announcements/delete': { req: AdminAnnouncementsDeleteRequest; res: EmptyResponse }; 'admin/announcements/list': { req: AdminAnnouncementsListRequest; res: AdminAnnouncementsListResponse }; 'admin/announcements/update': { req: AdminAnnouncementsUpdateRequest; res: EmptyResponse }; - 'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: EmptyResponse }; + 'admin/avatar-decorations/create': { req: AdminAvatarDecorationsCreateRequest; res: AdminAvatarDecorationsCreateResponse }; 'admin/avatar-decorations/delete': { req: AdminAvatarDecorationsDeleteRequest; res: EmptyResponse }; 'admin/avatar-decorations/list': { req: AdminAvatarDecorationsListRequest; res: AdminAvatarDecorationsListResponse }; 'admin/avatar-decorations/update': { req: AdminAvatarDecorationsUpdateRequest; res: EmptyResponse }; @@ -663,6 +667,8 @@ export type Endpoints = { 'admin/relays/remove': { req: AdminRelaysRemoveRequest; res: EmptyResponse }; 'admin/reset-password': { req: AdminResetPasswordRequest; res: AdminResetPasswordResponse }; 'admin/resolve-abuse-user-report': { req: AdminResolveAbuseUserReportRequest; res: EmptyResponse }; + 'admin/forward-abuse-user-report': { req: AdminForwardAbuseUserReportRequest; res: EmptyResponse }; + 'admin/update-abuse-user-report': { req: AdminUpdateAbuseUserReportRequest; res: EmptyResponse }; 'admin/send-email': { req: AdminSendEmailRequest; res: EmptyResponse }; 'admin/server-info': { req: EmptyRequest; res: AdminServerInfoResponse }; 'admin/show-moderation-logs': { req: AdminShowModerationLogsRequest; res: AdminShowModerationLogsResponse }; @@ -933,7 +939,7 @@ export type Endpoints = { 'pages/update': { req: PagesUpdateRequest; res: EmptyResponse }; 'flash/create': { req: FlashCreateRequest; res: FlashCreateResponse }; 'flash/delete': { req: FlashDeleteRequest; res: EmptyResponse }; - 'flash/featured': { req: EmptyRequest; res: FlashFeaturedResponse }; + 'flash/featured': { req: FlashFeaturedRequest; res: FlashFeaturedResponse }; 'flash/like': { req: FlashLikeRequest; res: EmptyResponse }; 'flash/show': { req: FlashShowRequest; res: FlashShowResponse }; 'flash/unlike': { req: FlashUnlikeRequest; res: EmptyResponse }; diff --git a/packages/misskey-js/src/autogen/entities.ts b/packages/misskey-js/src/autogen/entities.ts index 2da78f6a50..9166bb701f 100644 --- a/packages/misskey-js/src/autogen/entities.ts +++ b/packages/misskey-js/src/autogen/entities.ts @@ -34,6 +34,7 @@ export type AdminAnnouncementsListRequest = operations['admin___announcements___ export type AdminAnnouncementsListResponse = operations['admin___announcements___list']['responses']['200']['content']['application/json']; export type AdminAnnouncementsUpdateRequest = operations['admin___announcements___update']['requestBody']['content']['application/json']; export type AdminAvatarDecorationsCreateRequest = operations['admin___avatar-decorations___create']['requestBody']['content']['application/json']; +export type AdminAvatarDecorationsCreateResponse = operations['admin___avatar-decorations___create']['responses']['200']['content']['application/json']; export type AdminAvatarDecorationsDeleteRequest = operations['admin___avatar-decorations___delete']['requestBody']['content']['application/json']; export type AdminAvatarDecorationsListRequest = operations['admin___avatar-decorations___list']['requestBody']['content']['application/json']; export type AdminAvatarDecorationsListResponse = operations['admin___avatar-decorations___list']['responses']['200']['content']['application/json']; @@ -86,6 +87,8 @@ export type AdminRelaysRemoveRequest = operations['admin___relays___remove']['re export type AdminResetPasswordRequest = operations['admin___reset-password']['requestBody']['content']['application/json']; export type AdminResetPasswordResponse = operations['admin___reset-password']['responses']['200']['content']['application/json']; export type AdminResolveAbuseUserReportRequest = operations['admin___resolve-abuse-user-report']['requestBody']['content']['application/json']; +export type AdminForwardAbuseUserReportRequest = operations['admin___forward-abuse-user-report']['requestBody']['content']['application/json']; +export type AdminUpdateAbuseUserReportRequest = operations['admin___update-abuse-user-report']['requestBody']['content']['application/json']; export type AdminSendEmailRequest = operations['admin___send-email']['requestBody']['content']['application/json']; export type AdminServerInfoResponse = operations['admin___server-info']['responses']['200']['content']['application/json']; export type AdminShowModerationLogsRequest = operations['admin___show-moderation-logs']['requestBody']['content']['application/json']; @@ -492,6 +495,7 @@ export type PagesUpdateRequest = operations['pages___update']['requestBody']['co export type FlashCreateRequest = operations['flash___create']['requestBody']['content']['application/json']; export type FlashCreateResponse = operations['flash___create']['responses']['200']['content']['application/json']; export type FlashDeleteRequest = operations['flash___delete']['requestBody']['content']['application/json']; +export type FlashFeaturedRequest = operations['flash___featured']['requestBody']['content']['application/json']; export type FlashFeaturedResponse = operations['flash___featured']['responses']['200']['content']['application/json']; export type FlashLikeRequest = operations['flash___like']['requestBody']['content']['application/json']; export type FlashShowRequest = operations['flash___show']['requestBody']['content']['application/json']; diff --git a/packages/misskey-js/src/autogen/types.ts b/packages/misskey-js/src/autogen/types.ts index b161698f27..888e46e008 100644 --- a/packages/misskey-js/src/autogen/types.ts +++ b/packages/misskey-js/src/autogen/types.ts @@ -576,6 +576,24 @@ export type paths = { */ post: operations['admin___resolve-abuse-user-report']; }; + '/admin/forward-abuse-user-report': { + /** + * admin/forward-abuse-user-report + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + post: operations['admin___forward-abuse-user-report']; + }; + '/admin/update-abuse-user-report': { + /** + * admin/update-abuse-user-report + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + post: operations['admin___update-abuse-user-report']; + }; '/admin/send-email': { /** * admin/send-email @@ -3918,6 +3936,9 @@ export type components = { isCat?: boolean; speakAsCat?: boolean; isSilenced: boolean; + requireSigninToViewContents?: boolean; + makeNotesFollowersOnlyBefore?: number | null; + makeNotesHiddenBefore?: number | null; instance?: { name: string | null; softwareName: string | null; @@ -3987,16 +4008,13 @@ export type components = { followingVisibility: 'public' | 'followers' | 'private'; /** @enum {string} */ followersVisibility: 'public' | 'followers' | 'private'; - /** @default false */ - twoFactorEnabled: boolean; - /** @default false */ - usePasswordLessLogin: boolean; - /** @default false */ - securityKeys: boolean; roles: components['schemas']['RoleLite'][]; followedMessage?: string | null; memo: string | null; moderationNote?: string; + twoFactorEnabled?: boolean; + usePasswordLessLogin?: boolean; + securityKeys?: boolean; isFollowing?: boolean; isFollowed?: boolean; hasPendingFollowRequestFromYou?: boolean; @@ -4181,6 +4199,12 @@ export type components = { }[]; loggedInDays: number; policies: components['schemas']['RolePolicies']; + /** @default false */ + twoFactorEnabled: boolean; + /** @default false */ + usePasswordLessLogin: boolean; + /** @default false */ + securityKeys: boolean; email?: string | null; emailVerified?: boolean | null; securityKeysList?: { @@ -4497,7 +4521,14 @@ export type components = { exportedEntity: 'antenna' | 'blocking' | 'clip' | 'customEmoji' | 'favorite' | 'following' | 'muting' | 'note' | 'userList'; /** Format: id */ fileId: string; - }) | ({ + }) | { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** @enum {string} */ + type: 'login'; + } | ({ /** Format: id */ id: string; /** Format: date-time */ @@ -5193,6 +5224,7 @@ export type components = { enableFC: boolean; fcSiteKey: string | null; enableAchievements: boolean | null; + enableTestcaptcha: boolean; swPublickey: string | null; /** @default /assets/ai.png */ mascotImageUrl: string; @@ -5275,7 +5307,7 @@ export type components = { latestSentAt: string | null; latestStatus: number | null; name: string; - on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[]; + on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; url: string; secret: string; }; @@ -5333,6 +5365,7 @@ export type operations = { turnstileSiteKey: string | null; enableFC: boolean; fcSiteKey: string | null; + enableTestcaptcha: boolean; swPublickey: string | null; /** @default /assets/ai.png */ mascotImageUrl: string | null; @@ -5354,6 +5387,7 @@ export type operations = { blockedHosts: string[]; sensitiveWords: string[]; prohibitedWords: string[]; + prohibitedWordsForNameOfUser: string[]; bannedEmailDomains?: string[]; preservedUsernames: string[]; bubbleInstances: string[]; @@ -5397,6 +5431,7 @@ export type operations = { truemailAuthKey: string | null; enableChartsForRemoteUser: boolean; enableChartsForFederatedInstances: boolean; + enableStatsForFederatedInstances: boolean; enableServerMachineStats: boolean; enableAchievements: boolean; enableIdenticonGeneration: boolean; @@ -5510,8 +5545,6 @@ export type operations = { * @enum {string} */ targetUserOrigin?: 'combined' | 'local' | 'remote'; - /** @default false */ - forwarded?: boolean; }; }; }; @@ -5538,7 +5571,11 @@ export type operations = { assigneeId: string | null; reporter: components['schemas']['UserDetailedNotMe']; targetUser: components['schemas']['UserDetailedNotMe']; - assignee?: components['schemas']['UserDetailedNotMe'] | null; + assignee: components['schemas']['UserDetailedNotMe'] | null; + forwarded: boolean; + /** @enum {string|null} */ + resolvedAs: 'accept' | 'reject' | null; + moderationNote: string; })[]; }; }; @@ -5872,6 +5909,7 @@ export type operations = { 'application/json': { username: string; password: string; + setupPassword?: string | null; }; }; }; @@ -6554,9 +6592,22 @@ export type operations = { }; }; responses: { - /** @description OK (without any results) */ - 204: { - content: never; + /** @description OK (with results) */ + 200: { + content: { + 'application/json': { + /** Format: id */ + id: string; + /** Format: date-time */ + createdAt: string; + /** Format: date-time */ + updatedAt: string | null; + name: string; + description: string; + url: string; + roleIdsThatCanBeUsedThisDecoration: string[]; + }; + }; }; /** @description Client error */ 400: { @@ -8954,8 +9005,113 @@ export type operations = { 'application/json': { /** Format: misskey:id */ reportId: string; - /** @default false */ - forward?: boolean; + /** @enum {string|null} */ + resolvedAs?: 'accept' | 'reject' | null; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * admin/forward-abuse-user-report + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + 'admin___forward-abuse-user-report': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + reportId: string; + }; + }; + }; + responses: { + /** @description OK (without any results) */ + 204: { + content: never; + }; + /** @description Client error */ + 400: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Authentication error */ + 401: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Forbidden error */ + 403: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description I'm Ai */ + 418: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + /** @description Internal server error */ + 500: { + content: { + 'application/json': components['schemas']['Error']; + }; + }; + }; + }; + /** + * admin/update-abuse-user-report + * @description No description provided. + * + * **Credential required**: *Yes* / **Permission**: *write:admin:resolve-abuse-user-report* + */ + 'admin___update-abuse-user-report': { + requestBody: { + content: { + 'application/json': { + /** Format: misskey:id */ + reportId: string; + moderationNote?: string; }; }; }; @@ -9909,6 +10065,7 @@ export type operations = { blockedHosts?: string[] | null; sensitiveWords?: string[] | null; prohibitedWords?: string[] | null; + prohibitedWordsForNameOfUser?: string[] | null; themeColor?: string | null; mascotImageUrl?: string | null; bannerUrl?: string | null; @@ -9947,6 +10104,7 @@ export type operations = { enableFC?: boolean; fcSiteKey?: string | null; fcSecretKey?: string | null; + enableTestcaptcha?: boolean; /** @enum {string} */ sensitiveMediaDetection?: 'none' | 'all' | 'local' | 'remote'; /** @enum {string} */ @@ -10002,6 +10160,7 @@ export type operations = { truemailAuthKey?: string | null; enableChartsForRemoteUser?: boolean; enableChartsForFederatedInstances?: boolean; + enableStatsForFederatedInstances?: boolean; enableServerMachineStats?: boolean; enableAchievements?: boolean; enableIdenticonGeneration?: boolean; @@ -10705,7 +10864,7 @@ export type operations = { 'application/json': { isActive: boolean; name: string; - on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[]; + on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; url: string; secret: string; }; @@ -10815,7 +10974,7 @@ export type operations = { content: { 'application/json': { isActive?: boolean; - on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[]; + on?: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; }; }; }; @@ -10928,7 +11087,7 @@ export type operations = { id: string; isActive: boolean; name: string; - on: ('abuseReport' | 'abuseReportResolved' | 'userCreated')[]; + on: ('abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged')[]; url: string; secret: string; }; @@ -10987,7 +11146,7 @@ export type operations = { /** Format: misskey:id */ webhookId: string; /** @enum {string} */ - type: 'abuseReport' | 'abuseReportResolved' | 'userCreated'; + type: 'abuseReport' | 'abuseReportResolved' | 'userCreated' | 'inactiveModeratorsWarning' | 'inactiveModeratorsInvitationOnlyChanged'; override?: { url?: string; secret?: string; @@ -20004,8 +20163,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'pollVote' | 'groupInvited')[]; }; }; }; @@ -20072,8 +20231,8 @@ export type operations = { untilId?: string; /** @default true */ markAsRead?: boolean; - includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; - excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; + includeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; + excludeTypes?: ('note' | 'follow' | 'mention' | 'reply' | 'renote' | 'quote' | 'reaction' | 'pollEnded' | 'edited' | 'receiveFollowRequest' | 'followRequestAccepted' | 'roleAssigned' | 'achievementEarned' | 'exportCompleted' | 'login' | 'scheduledNoteFailed' | 'scheduledNotePosted' | 'app' | 'test' | 'reaction:grouped' | 'renote:grouped' | 'pollVote' | 'groupInvited')[]; }; }; }; @@ -21321,6 +21480,9 @@ export type operations = { noCrawle?: boolean; preventAiLearning?: boolean; noindex?: boolean; + requireSigninToViewContents?: boolean; + makeNotesFollowersOnlyBefore?: number | null; + makeNotesHiddenBefore?: number | null; enableRss?: boolean; isBot?: boolean; isCat?: boolean; @@ -24549,7 +24711,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24607,7 +24769,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -24692,7 +24854,7 @@ export type operations = { 'application/json': components['schemas']['Error']; }; }; - /** @description To many requests */ + /** @description Too many requests */ 429: { content: { 'application/json': components['schemas']['Error']; @@ -26395,6 +26557,16 @@ export type operations = { * **Credential required**: *No* */ flash___featured: { + requestBody: { + content: { + 'application/json': { + /** @default 0 */ + offset?: number; + /** @default 10 */ + limit?: number; + }; + }; + }; responses: { /** @description OK (with results) */ 200: { diff --git a/packages/misskey-js/src/consts.ts b/packages/misskey-js/src/consts.ts index 6e32060fb7..0faf3dddc4 100644 --- a/packages/misskey-js/src/consts.ts +++ b/packages/misskey-js/src/consts.ts @@ -151,6 +151,8 @@ export const moderationLogTypes = [ 'markSensitiveDriveFile', 'unmarkSensitiveDriveFile', 'resolveAbuseReport', + 'forwardAbuseReport', + 'updateAbuseReportNote', 'createInvitation', 'createAd', 'updateAd', @@ -349,7 +351,18 @@ export type ModerationLogPayloads = { resolveAbuseReport: { reportId: string; report: ReceivedAbuseReport; - forwarded: boolean; + forwarded?: boolean; + resolvedAs?: string | null; + }; + forwardAbuseReport: { + reportId: string; + report: ReceivedAbuseReport; + }; + updateAbuseReportNote: { + reportId: string; + report: ReceivedAbuseReport; + before: string; + after: string; }; createInvitation: { invitations: InviteCode[]; diff --git a/packages/misskey-js/src/entities.ts b/packages/misskey-js/src/entities.ts index f85574d9d5..efe5ba19fb 100644 --- a/packages/misskey-js/src/entities.ts +++ b/packages/misskey-js/src/entities.ts @@ -10,6 +10,7 @@ import { User, UserDetailedNotMe, } from './autogen/models.js'; +import type { AuthenticationResponseJSON, PublicKeyCredentialRequestOptionsJSON } from '@simplewebauthn/types'; export * from './autogen/entities.js'; export * from './autogen/models.js'; @@ -159,6 +160,12 @@ export type ModerationLog = { type: 'resolveAbuseReport'; info: ModerationLogPayloads['resolveAbuseReport']; } | { + type: 'forwardAbuseReport'; + info: ModerationLogPayloads['forwardAbuseReport']; +} | { + type: 'updateAbuseReportNote'; + info: ModerationLogPayloads['updateAbuseReportNote']; +} | { type: 'unsetUserAvatar'; info: ModerationLogPayloads['unsetUserAvatar']; } | { @@ -256,6 +263,7 @@ export type SignupRequest = { 'hcaptcha-response'?: string | null; 'g-recaptcha-response'?: string | null; 'turnstile-response'?: string | null; + 'm-captcha-response'?: string | null; } export type SignupResponse = MeDetailed & { @@ -271,26 +279,42 @@ export type SignupPendingResponse = { i: string, }; -export type SigninRequest = { +export type SigninFlowRequest = { username: string; - password: string; + password?: string; token?: string; + credential?: AuthenticationResponseJSON; + 'hcaptcha-response'?: string | null; + 'g-recaptcha-response'?: string | null; + 'turnstile-response'?: string | null; + 'm-captcha-response'?: string | null; +}; + +export type SigninFlowResponse = { + finished: true; + id: User['id']; + i: string; +} | { + finished: false; + next: 'captcha' | 'password' | 'totp'; +} | { + finished: false; + next: 'passkey'; + authRequest: PublicKeyCredentialRequestOptionsJSON; }; export type SigninWithPasskeyRequest = { - credential?: object; + credential?: AuthenticationResponseJSON; context?: string; }; -export type SigninWithPasskeyResponse = { - option?: object; - context?: string; - signinResponse?: SigninResponse; +export type SigninWithPasskeyInitResponse = { + option: PublicKeyCredentialRequestOptionsJSON; + context: string; }; -export type SigninResponse = { - id: User['id'], - i: string, +export type SigninWithPasskeyResponse = { + signinResponse: SigninFlowResponse & { finished: true }; }; type Values<T extends Record<PropertyKey, unknown>> = T[keyof T]; diff --git a/packages/misskey-js/src/streaming.ts b/packages/misskey-js/src/streaming.ts index ffb46c77f6..6e34ec1508 100644 --- a/packages/misskey-js/src/streaming.ts +++ b/packages/misskey-js/src/streaming.ts @@ -51,7 +51,7 @@ export default class Stream extends EventEmitter<StreamEvents> implements IStrea private idCounter = 0; constructor(origin: string, user: { token: string; } | null, options?: { - WebSocket?: WebSocket; + WebSocket?: _ReconnectingWebsocket.Options['WebSocket']; }) { super(); diff --git a/packages/misskey-reversi/package.json b/packages/misskey-reversi/package.json index 6386f40118..3a468a9369 100644 --- a/packages/misskey-reversi/package.json +++ b/packages/misskey-reversi/package.json @@ -22,14 +22,14 @@ "lint": "pnpm typecheck && pnpm eslint" }, "devDependencies": { - "@types/node": "20.11.5", + "@types/node": "22.9.0", "@typescript-eslint/eslint-plugin": "7.1.0", "@typescript-eslint/parser": "7.1.0", "execa": "8.0.1", - "nodemon": "3.0.2", - "typescript": "5.3.3", - "esbuild": "0.19.11", - "glob": "10.3.10" + "nodemon": "3.1.7", + "typescript": "5.6.3", + "esbuild": "0.24.0", + "glob": "11.0.0" }, "files": [ "built" diff --git a/packages/shared/eslint.config.js b/packages/shared/eslint.config.js index e9d27c4a72..0368d008c0 100644 --- a/packages/shared/eslint.config.js +++ b/packages/shared/eslint.config.js @@ -6,6 +6,7 @@ export default [ { files: ['**/*.cjs'], languageOptions: { + sourceType: 'commonjs', parserOptions: { sourceType: 'commonjs', }, @@ -25,4 +26,10 @@ export default [ globals: globals.node, }, }, + { + files: ['**/*.js', '**/*.cjs'], + rules: { + '@typescript-eslint/no-var-requires': 'off', + }, + }, ]; diff --git a/packages/sw/package.json b/packages/sw/package.json index ed63a02662..d8c7f3618c 100644 --- a/packages/sw/package.json +++ b/packages/sw/package.json @@ -9,7 +9,7 @@ "lint": "pnpm typecheck && pnpm eslint" }, "dependencies": { - "esbuild": "0.23.1", + "esbuild": "0.24.0", "idb-keyval": "6.2.1", "misskey-js": "workspace:*" }, @@ -18,7 +18,7 @@ "@typescript/lib-webworker": "npm:@types/serviceworker@0.0.67", "eslint-plugin-import": "2.30.0", "nodemon": "3.1.7", - "typescript": "5.6.2" + "typescript": "5.6.3" }, "type": "module" } diff --git a/packages/sw/src/scripts/create-notification.ts b/packages/sw/src/scripts/create-notification.ts index 4fda784dba..65e84e4609 100644 --- a/packages/sw/src/scripts/create-notification.ts +++ b/packages/sw/src/scripts/create-notification.ts @@ -210,6 +210,12 @@ async function composeNotification(data: PushNotificationDataMap[keyof PushNotif tag: `achievement:${data.body.achievement}`, }]; + case 'login': + return [i18n.ts._notification.login, { + badge: iconUrl('login-2'), + data, + }]; + case 'exportCompleted': { const entityName = { antenna: i18n.ts.antennas, diff --git a/packages/sw/src/types.ts b/packages/sw/src/types.ts index fac3e707d8..4f82779808 100644 --- a/packages/sw/src/types.ts +++ b/packages/sw/src/types.ts @@ -50,4 +50,5 @@ export type BadgeNames = | 'quote' | 'repeat' | 'user-plus' - | 'users'; + | 'users' + | 'login-2'; diff --git a/pnpm-lock.yaml b/pnpm-lock.yaml index 1c5b624b05..d01454e892 100644 --- a/pnpm-lock.yaml +++ b/pnpm-lock.yaml @@ -14,10 +14,10 @@ importers: dependencies: cssnano: specifier: 6.1.2 - version: 6.1.2(postcss@8.4.47) + version: 6.1.2(postcss@8.4.49) esbuild: - specifier: 0.23.1 - version: 0.23.1 + specifier: 0.24.0 + version: 0.24.0 execa: specifier: 8.0.1 version: 8.0.1 @@ -34,43 +34,43 @@ importers: specifier: 4.1.0 version: 4.1.0 postcss: - specifier: 8.4.47 - version: 8.4.47 + specifier: 8.4.49 + version: 8.4.49 tar: specifier: 6.2.1 version: 6.2.1 terser: - specifier: 5.33.0 - version: 5.33.0 + specifier: 5.36.0 + version: 5.36.0 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 optionalDependencies: cypress: - specifier: 13.14.2 - version: 13.14.2 + specifier: 13.15.2 + version: 13.15.2 devDependencies: '@misskey-dev/eslint-plugin': specifier: 2.0.3 - version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0) + version: 2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0)(globals@15.12.0) '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) cross-env: specifier: 7.0.3 version: 7.0.3 eslint: - specifier: 9.8.0 - version: 9.8.0 + specifier: 9.14.0 + version: 9.14.0 globals: - specifier: 15.9.0 - version: 15.9.0 + specifier: 15.12.0 + version: 15.12.0 ncp: specifier: 2.0.0 version: 2.0.0 @@ -87,41 +87,41 @@ importers: specifier: 3.620.0 version: 3.620.0(@aws-sdk/client-s3@3.620.0) '@bull-board/api': - specifier: 6.0.0 - version: 6.0.0(@bull-board/ui@6.0.0) + specifier: 6.5.0 + version: 6.5.0(@bull-board/ui@6.5.0) '@bull-board/fastify': - specifier: 6.0.0 - version: 6.0.0 + specifier: 6.5.0 + version: 6.5.0 '@bull-board/ui': - specifier: 6.0.0 - version: 6.0.0 + specifier: 6.5.0 + version: 6.5.0 '@discordapp/twemoji': specifier: 15.1.0 version: 15.1.0 '@fastify/accepts': - specifier: 5.0.0 - version: 5.0.0 + specifier: 5.0.1 + version: 5.0.1 '@fastify/cookie': - specifier: 10.0.0 - version: 10.0.0 + specifier: 11.0.1 + version: 11.0.1 '@fastify/cors': - specifier: 10.0.0 - version: 10.0.0 + specifier: 10.0.1 + version: 10.0.1 '@fastify/express': - specifier: 4.0.0 - version: 4.0.0 + specifier: 4.0.1 + version: 4.0.1 '@fastify/http-proxy': - specifier: 10.0.0 - version: 10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + specifier: 10.0.1 + version: 10.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3) '@fastify/multipart': - specifier: 9.0.0 - version: 9.0.0 + specifier: 9.0.1 + version: 9.0.1 '@fastify/static': - specifier: 8.0.0 - version: 8.0.0 + specifier: 8.0.2 + version: 8.0.2 '@fastify/view': - specifier: 10.0.0 - version: 10.0.0 + specifier: 10.0.1 + version: 10.0.1 '@misskey-dev/sharp-read-bmp': specifier: 1.2.0 version: 1.2.0 @@ -132,23 +132,23 @@ importers: specifier: 0.1.56 version: 0.1.56 '@nestjs/common': - specifier: 10.4.3 - version: 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.7 + version: 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/core': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + specifier: 10.4.7 + version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nestjs/testing': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)) + specifier: 10.4.7 + version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)) '@peertube/http-signature': specifier: 1.7.0 version: 1.7.0 '@sentry/node': - specifier: 8.20.0 - version: 8.20.0 + specifier: 8.38.0 + version: 8.38.0 '@sentry/profiling-node': - specifier: 8.20.0 - version: 8.20.0 + specifier: 8.38.0 + version: 8.38.0 '@simplewebauthn/server': specifier: 10.0.1 version: 10.0.1(encoding@0.1.13) @@ -160,10 +160,10 @@ importers: version: 2.5.0 '@swc/cli': specifier: 0.3.12 - version: 0.3.12(@swc/core@1.6.6)(chokidar@3.5.3) + version: 0.3.12(@swc/core@1.9.2)(chokidar@3.5.3) '@swc/core': - specifier: 1.6.6 - version: 1.6.6 + specifier: 1.9.2 + version: 1.9.2 '@transfem-org/sfm-js': specifier: 0.24.5 version: 0.24.5 @@ -198,8 +198,8 @@ importers: specifier: 1.20.3 version: 1.20.3 bullmq: - specifier: 5.13.2 - version: 5.13.2 + specifier: 5.26.1 + version: 5.26.1 cacheable-lookup: specifier: 7.0.0 version: 7.0.0 @@ -246,23 +246,23 @@ importers: specifier: 4.2.2 version: 4.2.2 file-type: - specifier: 19.5.0 - version: 19.5.0 + specifier: 19.6.0 + version: 19.6.0 fluent-ffmpeg: specifier: 2.1.3 version: 2.1.3 form-data: - specifier: 4.0.0 - version: 4.0.0 + specifier: 4.0.1 + version: 4.0.1 glob: specifier: 11.0.0 version: 11.0.0 got: - specifier: 14.4.2 - version: 14.4.2 + specifier: 14.4.4 + version: 14.4.4 happy-dom: - specifier: 15.7.4 - version: 15.7.4 + specifier: 15.11.4 + version: 15.11.4 hpagent: specifier: 1.2.0 version: 1.2.0 @@ -306,8 +306,8 @@ importers: specifier: workspace:* version: link:../megalodon meilisearch: - specifier: 0.42.0 - version: 0.42.0(encoding@0.1.13) + specifier: 0.45.0 + version: 0.45.0 microformats-parser: specifier: 2.0.2 version: 2.0.2 @@ -324,8 +324,8 @@ importers: specifier: 3.0.0-canary.1 version: 3.0.0-canary.1 nanoid: - specifier: 5.0.7 - version: 5.0.7 + specifier: 5.0.8 + version: 5.0.8 nested-property: specifier: 4.0.0 version: 4.0.0 @@ -333,8 +333,8 @@ importers: specifier: 3.3.2 version: 3.3.2 nodemailer: - specifier: 6.9.15 - version: 6.9.15 + specifier: 6.9.16 + version: 6.9.16 oauth: specifier: 0.10.0 version: 0.10.0 @@ -348,14 +348,14 @@ importers: specifier: 0.0.14 version: 0.0.14 otpauth: - specifier: 9.3.2 - version: 9.3.2 + specifier: 9.3.4 + version: 9.3.4 parse5: - specifier: 7.1.2 - version: 7.1.2 + specifier: 7.2.1 + version: 7.2.1 pg: - specifier: 8.13.0 - version: 8.13.0 + specifier: 8.13.1 + version: 8.13.1 pkce-challenge: specifier: 4.1.0 version: 4.1.0 @@ -405,8 +405,8 @@ importers: specifier: 7.8.1 version: 7.8.1 sanitize-html: - specifier: 2.13.0 - version: 2.13.0 + specifier: 2.13.1 + version: 2.13.1 secure-json-parse: specifier: 2.7.0 version: 2.7.0 @@ -439,10 +439,10 @@ importers: version: 4.2.0 typeorm: specifier: 0.3.20 - version: 0.3.20(ioredis@5.4.1)(pg@8.13.0) + version: 0.3.20(ioredis@5.4.1)(pg@8.13.1) typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 ulid: specifier: 2.3.0 version: 2.3.0 @@ -548,20 +548,20 @@ importers: specifier: 29.7.0 version: 29.7.0 '@nestjs/platform-express': - specifier: 10.4.3 - version: 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) + specifier: 10.4.7 + version: 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) '@simplewebauthn/types': specifier: 10.0.0 version: 10.0.0 '@swc/jest': - specifier: 0.2.36 - version: 0.2.36(@swc/core@1.6.6) + specifier: 0.2.37 + version: 0.2.37(@swc/core@1.9.2) '@types/accepts': specifier: 1.3.7 version: 1.3.7 '@types/archiver': - specifier: 6.0.2 - version: 6.0.2 + specifier: 6.0.3 + version: 6.0.3 '@types/bcryptjs': specifier: 2.4.6 version: 2.4.6 @@ -569,14 +569,14 @@ importers: specifier: 1.19.5 version: 1.19.5 '@types/color-convert': - specifier: 2.0.3 - version: 2.0.3 + specifier: 2.0.4 + version: 2.0.4 '@types/content-disposition': specifier: 0.5.8 version: 0.5.8 '@types/fluent-ffmpeg': - specifier: 2.1.26 - version: 2.1.26 + specifier: 2.1.27 + version: 2.1.27 '@types/htmlescape': specifier: 1.1.3 version: 1.1.3 @@ -584,8 +584,8 @@ importers: specifier: 1.0.7 version: 1.0.7 '@types/jest': - specifier: 29.5.13 - version: 29.5.13 + specifier: 29.5.14 + version: 29.5.14 '@types/js-yaml': specifier: 4.0.9 version: 4.0.9 @@ -605,14 +605,14 @@ importers: specifier: 0.7.34 version: 0.7.34 '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@types/nodemailer': specifier: 6.4.16 version: 6.4.16 '@types/oauth': - specifier: 0.9.5 - version: 0.9.5 + specifier: 0.9.6 + version: 0.9.6 '@types/oauth2orize': specifier: 1.11.5 version: 1.11.5 @@ -668,17 +668,17 @@ importers: specifier: 1.1.3 version: 1.1.3 '@types/web-push': - specifier: 3.6.3 - version: 3.6.3 + specifier: 3.6.4 + version: 3.6.4 '@types/ws': - specifier: 8.5.12 - version: 8.5.12 + specifier: 8.5.13 + version: 8.5.13 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) aws-sdk-client-mock: specifier: 4.0.1 version: 4.0.1 @@ -687,16 +687,16 @@ importers: version: 7.0.3 eslint-plugin-import: specifier: 2.30.0 - version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0) execa: - specifier: 9.4.0 - version: 9.4.0 + specifier: 8.0.1 + version: 8.0.1 fkill: specifier: 9.0.0 version: 9.0.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.14.12) + version: 29.7.0(@types/node@22.9.0) jest-mock: specifier: 29.7.0 version: 29.7.0 @@ -729,13 +729,13 @@ importers: version: 2.1.1 '@rollup/plugin-json': specifier: 6.1.0 - version: 6.1.0(rollup@4.22.5) + version: 6.1.0(rollup@4.26.0) '@rollup/plugin-replace': specifier: 5.0.7 - version: 5.0.7(rollup@4.22.5) + version: 5.0.7(rollup@4.26.0) '@rollup/pluginutils': - specifier: 5.1.2 - version: 5.1.2(rollup@4.22.5) + specifier: 5.1.3 + version: 5.1.3(rollup@4.26.0) '@ruffle-rs/ruffle': specifier: 0.1.0-nightly.2024.10.15 version: 0.1.0-nightly.2024.10.15 @@ -749,11 +749,11 @@ importers: specifier: 15.1.1 version: 15.1.1 '@vitejs/plugin-vue': - specifier: 5.1.4 - version: 5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2)) + specifier: 5.2.0 + version: 5.2.0(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3)) '@vue/compiler-sfc': - specifier: 3.5.10 - version: 3.5.10 + specifier: 3.5.12 + version: 3.5.12 aiscript-vscode: specifier: github:aiscript-dev/aiscript-vscode#v0.1.11 version: https://codeload.github.com/aiscript-dev/aiscript-vscode/tar.gz/e1e1b27f2f72cd28a473e004b6da0d8fc0bd40d9 @@ -770,23 +770,23 @@ importers: specifier: 1.9.3 version: 1.9.3 chart.js: - specifier: 4.4.4 - version: 4.4.4 + specifier: 4.4.6 + version: 4.4.6 chartjs-adapter-date-fns: specifier: 3.0.0 - version: 3.0.0(chart.js@4.4.4)(date-fns@2.30.0) + version: 3.0.0(chart.js@4.4.6)(date-fns@2.30.0) chartjs-chart-matrix: specifier: 2.0.1 - version: 2.0.1(chart.js@4.4.4) + version: 2.0.1(chart.js@4.4.6) chartjs-plugin-gradient: specifier: 0.6.1 - version: 0.6.1(chart.js@4.4.4) + version: 0.6.1(chart.js@4.4.6) chartjs-plugin-zoom: specifier: 2.0.1 - version: 2.0.1(chart.js@4.4.4) + version: 2.0.1(chart.js@4.4.6) chromatic: - specifier: 11.10.4 - version: 11.10.4 + specifier: 11.18.1 + version: 11.18.1 compare-versions: specifier: 6.1.1 version: 6.1.1 @@ -839,17 +839,17 @@ importers: specifier: 2.3.1 version: 2.3.1 rollup: - specifier: 4.22.5 - version: 4.22.5 + specifier: 4.26.0 + version: 4.26.0 sanitize-html: - specifier: 2.13.0 - version: 2.13.0 + specifier: 2.13.1 + version: 2.13.1 sass: specifier: 1.79.3 version: 1.79.3 shiki: - specifier: 1.12.0 - version: 1.12.0 + specifier: 1.22.2 + version: 1.22.2 strict-event-emitter-types: specifier: 2.0.0 version: 2.0.0 @@ -872,88 +872,91 @@ importers: specifier: 4.2.0 version: 4.2.0 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 uuid: specifier: 10.0.0 version: 10.0.0 v-code-diff: specifier: 1.13.1 - version: 1.13.1(vue@3.5.10(typescript@5.6.2)) + version: 1.13.1(vue@3.5.12(typescript@5.6.3)) vite: - specifier: 5.4.8 - version: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + specifier: 5.4.11 + version: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) vue: - specifier: 3.5.10 - version: 3.5.10(typescript@5.6.2) + specifier: 3.5.12 + version: 3.5.12(typescript@5.6.3) vuedraggable: specifier: next - version: 4.1.0(vue@3.5.10(typescript@5.6.2)) + version: 4.1.0(vue@3.5.12(typescript@5.6.3)) optionalDependencies: cypress: - specifier: 13.15.0 - version: 13.15.0 + specifier: 13.15.2 + version: 13.15.2 devDependencies: '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 '@storybook/addon-actions': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/addon-essentials': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(@types/react@18.0.28)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/addon-interactions': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/addon-links': - specifier: 8.3.3 - version: 8.3.3(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/addon-mdx-gfm': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/addon-storysource': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/blocks': - specifier: 8.3.3 - version: 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/components': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/core-events': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/manager-api': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/preview-api': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/react': - specifier: 8.3.3 - version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2) + specifier: 8.4.4 + version: 8.4.4(@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(typescript@5.6.3) '@storybook/react-vite': - specifier: 8.3.3 - version: 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.5)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) + specifier: 8.4.4 + version: 8.4.4(@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.26.0)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)) '@storybook/test': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/theming': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/types': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/vue3': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(typescript@5.6.2)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vue@3.5.12(typescript@5.6.3)) '@storybook/vue3-vite': - specifier: 8.3.3 - version: 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2)) + specifier: 8.4.4 + version: 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3)) '@testing-library/vue': specifier: 8.1.0 - version: 8.1.0(@vue/compiler-sfc@3.5.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) + version: 8.1.0(@vue/compiler-sfc@3.5.12)(@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)) + '@types/canvas-confetti': + specifier: ^1.6.4 + version: 1.6.4 '@types/estree': specifier: 1.0.6 version: 1.0.6 @@ -967,8 +970,8 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@types/punycode': specifier: 2.1.4 version: 2.1.4 @@ -988,32 +991,32 @@ importers: specifier: 10.0.0 version: 10.0.0 '@types/ws': - specifier: 8.5.12 - version: 8.5.12 + specifier: 8.5.13 + version: 8.5.13 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)) + version: 1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0)) '@vue/runtime-core': - specifier: 3.5.10 - version: 3.5.10 + specifier: 3.5.12 + version: 3.5.12 acorn: - specifier: 8.12.1 - version: 8.12.1 + specifier: 8.14.0 + version: 8.14.0 cross-env: specifier: 7.0.3 version: 7.0.3 eslint-plugin-import: - specifier: 2.30.0 - version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) + specifier: 2.31.0 + version: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0) eslint-plugin-vue: - specifier: 9.28.0 - version: 9.28.0(eslint@9.8.0) + specifier: 9.31.0 + version: 9.31.0(eslint@9.14.0) fast-glob: specifier: 3.3.2 version: 3.3.2 @@ -1027,11 +1030,11 @@ importers: specifier: 4.0.8 version: 4.0.8 msw: - specifier: 2.4.9 - version: 2.4.9(typescript@5.6.2) + specifier: 2.6.4 + version: 2.6.4(@types/node@22.9.0)(typescript@5.6.3) msw-storybook-addon: - specifier: 2.0.3 - version: 2.0.3(msw@2.4.9(typescript@5.6.2)) + specifier: 2.0.4 + version: 2.0.4(msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3)) nodemon: specifier: 3.1.7 version: 3.1.7 @@ -1051,29 +1054,29 @@ importers: specifier: 2.0.8 version: 2.0.8 storybook: - specifier: 8.3.3 - version: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + specifier: 8.4.4 + version: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) storybook-addon-misskey-theme: specifier: github:misskey-dev/storybook-addon-misskey-theme - version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) + version: https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/components@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/core-events@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/theming@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/types@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1) vite-plugin-turbosnap: specifier: 1.0.3 version: 1.0.3 vitest: specifier: 1.6.0 - version: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) + version: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0) vitest-fetch-mock: specifier: 0.2.2 - version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)) + version: 0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0)) vue-component-type-helpers: - specifier: 2.1.6 - version: 2.1.6 + specifier: 2.1.10 + version: 2.1.10 vue-eslint-parser: specifier: 9.4.3 - version: 9.4.3(eslint@9.8.0) + version: 9.4.3(eslint@9.14.0) vue-tsc: - specifier: 2.1.6 - version: 2.1.6(typescript@5.6.2) + specifier: 2.1.10 + version: 2.1.10(typescript@5.6.3) packages/frontend-embed: dependencies: @@ -1085,13 +1088,13 @@ importers: version: 2.1.1 '@rollup/plugin-json': specifier: 6.1.0 - version: 6.1.0(rollup@4.22.5) + version: 6.1.0(rollup@4.26.0) '@rollup/plugin-replace': specifier: 5.0.7 - version: 5.0.7(rollup@4.22.5) + version: 5.0.7(rollup@4.26.0) '@rollup/pluginutils': - specifier: 5.1.2 - version: 5.1.2(rollup@4.22.5) + specifier: 5.1.3 + version: 5.1.3(rollup@4.26.0) '@transfem-org/sfm-js': specifier: 0.24.5 version: 0.24.5 @@ -1099,11 +1102,11 @@ importers: specifier: 15.1.1 version: 15.1.1 '@vitejs/plugin-vue': - specifier: 5.1.4 - version: 5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2)) + specifier: 5.2.0 + version: 5.2.0(vite@5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3)) '@vue/compiler-sfc': - specifier: 3.5.10 - version: 3.5.10 + specifier: 3.5.12 + version: 3.5.12 astring: specifier: 1.9.0 version: 1.9.0 @@ -1126,14 +1129,14 @@ importers: specifier: 2.3.1 version: 2.3.1 rollup: - specifier: 4.22.5 - version: 4.22.5 + specifier: 4.26.0 + version: 4.26.0 sass: - specifier: 1.79.3 - version: 1.79.3 + specifier: 1.79.4 + version: 1.79.4 shiki: - specifier: 1.12.0 - version: 1.12.0 + specifier: 1.22.2 + version: 1.22.2 tinycolor2: specifier: 1.6.0 version: 1.6.0 @@ -1144,24 +1147,24 @@ importers: specifier: 4.2.0 version: 4.2.0 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 uuid: specifier: 10.0.0 version: 10.0.0 vite: - specifier: 5.4.8 - version: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + specifier: 5.4.11 + version: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) vue: - specifier: 3.5.10 - version: 3.5.10(typescript@5.6.2) + specifier: 3.5.12 + version: 3.5.12(typescript@5.6.3) devDependencies: '@misskey-dev/summaly': specifier: 5.1.0 version: 5.1.0 '@testing-library/vue': specifier: 8.1.0 - version: 8.1.0(@vue/compiler-sfc@3.5.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) + version: 8.1.0(@vue/compiler-sfc@3.5.12)(@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)) '@types/estree': specifier: 1.0.6 version: 1.0.6 @@ -1169,8 +1172,8 @@ importers: specifier: 4.0.9 version: 4.0.9 '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@types/punycode': specifier: 2.1.4 version: 2.1.4 @@ -1181,32 +1184,32 @@ importers: specifier: 10.0.0 version: 10.0.0 '@types/ws': - specifier: 8.5.12 - version: 8.5.12 + specifier: 8.5.13 + version: 8.5.13 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@vitest/coverage-v8': specifier: 1.6.0 - version: 1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)) + version: 1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0)) '@vue/runtime-core': - specifier: 3.5.10 - version: 3.5.10 + specifier: 3.5.12 + version: 3.5.12 acorn: - specifier: 8.12.1 - version: 8.12.1 + specifier: 8.14.0 + version: 8.14.0 cross-env: specifier: 7.0.3 version: 7.0.3 eslint-plugin-import: - specifier: 2.30.0 - version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) + specifier: 2.31.0 + version: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0) eslint-plugin-vue: - specifier: 9.28.0 - version: 9.28.0(eslint@9.8.0) + specifier: 9.31.0 + version: 9.31.0(eslint@9.14.0) fast-glob: specifier: 3.3.2 version: 3.3.2 @@ -1220,8 +1223,8 @@ importers: specifier: 4.0.8 version: 4.0.8 msw: - specifier: 2.3.4 - version: 2.3.4(typescript@5.6.2) + specifier: 2.6.4 + version: 2.6.4(@types/node@22.9.0)(typescript@5.6.3) nodemon: specifier: 3.1.7 version: 3.1.7 @@ -1235,14 +1238,14 @@ importers: specifier: 1.0.3 version: 1.0.3 vue-component-type-helpers: - specifier: 2.1.6 - version: 2.1.6 + specifier: 2.1.10 + version: 2.1.10 vue-eslint-parser: specifier: 9.4.3 - version: 9.4.3(eslint@9.8.0) + version: 9.4.3(eslint@9.14.0) vue-tsc: - specifier: 2.1.6 - version: 2.1.6(typescript@5.6.2) + specifier: 2.1.10 + version: 2.1.10(typescript@5.6.3) packages/frontend-shared: dependencies: @@ -1250,30 +1253,30 @@ importers: specifier: workspace:* version: link:../misskey-js vue: - specifier: 3.4.37 - version: 3.4.37(typescript@5.5.4) + specifier: 3.5.12 + version: 3.5.12(typescript@5.6.3) devDependencies: '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.5.4) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) esbuild: - specifier: 0.23.0 - version: 0.23.0 + specifier: 0.24.0 + version: 0.24.0 eslint-plugin-vue: - specifier: 9.27.0 - version: 9.27.0(eslint@9.8.0) + specifier: 9.31.0 + version: 9.31.0(eslint@9.14.0) typescript: - specifier: 5.5.4 - version: 5.5.4 + specifier: 5.6.3 + version: 5.6.3 vue-eslint-parser: specifier: 9.4.3 - version: 9.4.3(eslint@9.8.0) + version: 9.4.3(eslint@9.14.0) packages/megalodon: dependencies: @@ -1349,7 +1352,7 @@ importers: version: 9.1.0(eslint@8.57.0) jest: specifier: ^29.7.0 - version: 29.7.0(@types/node@20.14.12) + version: 29.7.0(@types/node@22.9.0) jest-worker: specifier: ^29.7.0 version: 29.7.0 @@ -1361,7 +1364,7 @@ importers: version: 3.3.3 ts-jest: specifier: ^29.1.1 - version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6) + version: 29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6) typedoc: specifier: ^0.25.3 version: 0.25.13(typescript@5.1.6) @@ -1379,38 +1382,41 @@ importers: version: 3.0.5 devDependencies: '@types/matter-js': - specifier: 0.19.6 - version: 0.19.6 + specifier: 0.19.7 + version: 0.19.7 '@types/node': - specifier: 20.11.5 - version: 20.11.5 + specifier: 22.9.0 + version: 22.9.0 '@types/seedrandom': specifier: 3.0.8 version: 3.0.8 '@typescript-eslint/eslint-plugin': specifier: 7.1.0 - version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.1.0 - version: 7.1.0(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(eslint@9.14.0)(typescript@5.6.3) esbuild: - specifier: 0.19.11 - version: 0.19.11 + specifier: 0.24.0 + version: 0.24.0 execa: specifier: 8.0.1 version: 8.0.1 glob: - specifier: 10.3.10 - version: 10.3.10 + specifier: 11.0.0 + version: 11.0.0 nodemon: - specifier: 3.0.2 - version: 3.0.2 + specifier: 3.1.7 + version: 3.1.7 typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.6.3 + version: 5.6.3 packages/misskey-js: dependencies: + '@simplewebauthn/types': + specifier: 11.0.0 + version: 11.0.0 eventemitter3: specifier: 5.0.1 version: 5.0.1 @@ -1419,35 +1425,35 @@ importers: version: 4.4.0 devDependencies: '@microsoft/api-extractor': - specifier: 7.47.9 - version: 7.47.9(@types/node@20.14.12) + specifier: 7.47.11 + version: 7.47.11(@types/node@22.9.0) '@swc/jest': - specifier: 0.2.36 - version: 0.2.36(@swc/core@1.7.36) + specifier: 0.2.37 + version: 0.2.37(@swc/core@1.9.2) '@types/jest': - specifier: 29.5.13 - version: 29.5.13 + specifier: 29.5.14 + version: 29.5.14 '@types/node': - specifier: 20.14.12 - version: 20.14.12 + specifier: 22.9.0 + version: 22.9.0 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) esbuild: - specifier: 0.23.1 - version: 0.23.1 + specifier: 0.24.0 + version: 0.24.0 execa: - specifier: 9.4.0 - version: 9.4.0 + specifier: 8.0.1 + version: 8.0.1 glob: specifier: 11.0.0 version: 11.0.0 jest: specifier: 29.7.0 - version: 29.7.0(@types/node@20.14.12) + version: 29.7.0(@types/node@22.9.0) jest-fetch-mock: specifier: 3.0.3 version: 3.0.3(encoding@0.1.13) @@ -1467,8 +1473,8 @@ importers: specifier: 0.31.2 version: 0.31.2 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 packages/misskey-js/generator: devDependencies: @@ -1476,14 +1482,14 @@ importers: specifier: 2.6.0 version: 2.6.0(openapi-types@12.1.3) '@types/node': - specifier: 20.9.1 - version: 20.9.1 + specifier: 22.9.0 + version: 22.9.0 '@typescript-eslint/eslint-plugin': specifier: 7.17.0 - version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) openapi-types: specifier: 12.1.3 version: 12.1.3 @@ -1491,14 +1497,14 @@ importers: specifier: 6.7.3 version: 6.7.3 ts-case-convert: - specifier: 2.0.7 - version: 2.0.7 + specifier: 2.1.0 + version: 2.1.0 tsx: specifier: 4.4.0 version: 4.4.0 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 packages/misskey-reversi: dependencies: @@ -1507,35 +1513,35 @@ importers: version: 1.2.2 devDependencies: '@types/node': - specifier: 20.11.5 - version: 20.11.5 + specifier: 22.9.0 + version: 22.9.0 '@typescript-eslint/eslint-plugin': specifier: 7.1.0 - version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/parser': specifier: 7.1.0 - version: 7.1.0(eslint@9.8.0)(typescript@5.3.3) + version: 7.1.0(eslint@9.14.0)(typescript@5.6.3) esbuild: - specifier: 0.19.11 - version: 0.19.11 + specifier: 0.24.0 + version: 0.24.0 execa: specifier: 8.0.1 version: 8.0.1 glob: - specifier: 10.3.10 - version: 10.3.10 + specifier: 11.0.0 + version: 11.0.0 nodemon: - specifier: 3.0.2 - version: 3.0.2 + specifier: 3.1.7 + version: 3.1.7 typescript: - specifier: 5.3.3 - version: 5.3.3 + specifier: 5.6.3 + version: 5.6.3 packages/sw: dependencies: esbuild: - specifier: 0.23.1 - version: 0.23.1 + specifier: 0.24.0 + version: 0.24.0 idb-keyval: specifier: 6.2.1 version: 6.2.1 @@ -1545,19 +1551,19 @@ importers: devDependencies: '@typescript-eslint/parser': specifier: 7.17.0 - version: 7.17.0(eslint@9.8.0)(typescript@5.6.2) + version: 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@typescript/lib-webworker': specifier: npm:@types/serviceworker@0.0.67 version: '@types/serviceworker@0.0.67' eslint-plugin-import: specifier: 2.30.0 - version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) + version: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0) nodemon: specifier: 3.1.7 version: 3.1.7 typescript: - specifier: 5.6.2 - version: 5.6.2 + specifier: 5.6.3 + version: 5.6.3 packages: @@ -1749,14 +1755,26 @@ packages: resolution: {integrity: sha512-l9XxNcA4HX98rwCC2/KoiWcmEiRfZe4G+mYwDbCFT87JIMj6GBhLDkAzr/W8KAaA2IDr8Vc6J8fZPgVulxxfMA==} engines: {node: '>=16.0.0'} + '@babel/code-frame@7.23.5': + resolution: {integrity: sha512-CgH3s1a96LipHCmSUmYFPwY7MNx8C3avkq7i4Wl3cfa662ldtUe4VM1TPXX70pfmrlWTb6jLqTYrZyT2ZTJBgA==} + engines: {node: '>=6.9.0'} + '@babel/code-frame@7.24.7': resolution: {integrity: sha512-BcYH1CVJBO9tvyIZ2jVeXgSIMvGZ2FDRvDdOIVQyuklNKSsx+eppDEBq/g47Ayw+RqNFE+URvOShmf+f/qwAlA==} engines: {node: '>=6.9.0'} + '@babel/compat-data@7.23.5': + resolution: {integrity: sha512-uU27kfDRlhfKl+w1U6vp16IuvSLtjAxdArVXPa9BvLkrr7CYIsxH5adpHObeAGY/41+syctUWOZ140a2Rvkgjw==} + engines: {node: '>=6.9.0'} + '@babel/compat-data@7.24.7': resolution: {integrity: sha512-qJzAIcv03PyaWqxRgO4mSU3lihncDT296vnyuE2O8uA4w3UHWI4S3hgeZd1L8W1Bft40w9JxJ2b412iDUFFRhw==} engines: {node: '>=6.9.0'} + '@babel/core@7.23.5': + resolution: {integrity: sha512-Cwc2XjUrG4ilcfOw4wBAK+enbdgwAcAJCfGUItPBKR7Mjw4aEfAFYrLxeRp4jWgtNIKn3n2AlBOfwwafl+42/g==} + engines: {node: '>=6.9.0'} + '@babel/core@7.24.7': resolution: {integrity: sha512-nykK+LEK86ahTkX/3TgauT0ikKoNCfKHEaZYTUVupJdTLzGNvrblu4u6fa7DhZONAltdf8e662t/abY8idrd/g==} engines: {node: '>=6.9.0'} @@ -1765,6 +1783,10 @@ packages: resolution: {integrity: sha512-oipXieGC3i45Y1A41t4tAqpnEZWgB/lC6Ehh6+rOviR5XWpTtMmLN+fGjz9vOiNRt0p6RtO6DtD0pdU3vpqdSA==} engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.22.15': + resolution: {integrity: sha512-y6EEzULok0Qvz8yyLkCvVX+02ic+By2UdOhylwUOvOn9dvYc9mKICJuuU1n1XBI02YWsNsnrY1kc6DVbjcXbtw==} + engines: {node: '>=6.9.0'} + '@babel/helper-compilation-targets@7.24.7': resolution: {integrity: sha512-ctSdRHBi20qWOfy27RUb4Fhp07KSJ3sXcuSvTrXrc4aG8NSYDo1ici3Vhg9bg69y5bj0Mr1lh0aeEgTvc12rMg==} engines: {node: '>=6.9.0'} @@ -1781,10 +1803,20 @@ packages: resolution: {integrity: sha512-MJJwhkoGy5c4ehfoRyrJ/owKeMl19U54h27YYftT0o2teQ3FJ3nQUf/I3LlJsX4l3qlw7WRXUmiyajvHXoTubQ==} engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.22.15': + resolution: {integrity: sha512-0pYVBnDKZO2fnSPCrgM/6WMc7eS20Fbok+0r88fp+YtWVLZrp4CkafFGIp+W0VKw4a22sgebPT99y+FDNMdP4w==} + engines: {node: '>=6.9.0'} + '@babel/helper-module-imports@7.24.7': resolution: {integrity: sha512-8AyH3C+74cgCVVXow/myrynrAGv+nTVg5vKu2nZph9x7RcRwzmh0VFallJuFTZ9mx6u4eSdXZfcOzSqTUm0HCA==} engines: {node: '>=6.9.0'} + '@babel/helper-module-transforms@7.23.3': + resolution: {integrity: sha512-7bBs4ED9OmswdfDzpz4MpWgSrV7FXlc3zIagvLFjS5H+Mk7Snr21vQ6QwrsoCGMfNC4e4LQPdoULEt4ykz0SRQ==} + engines: {node: '>=6.9.0'} + peerDependencies: + '@babel/core': ^7.0.0 + '@babel/helper-module-transforms@7.24.7': resolution: {integrity: sha512-1fuJEwIrp+97rM4RWdO+qrRsZlAeL1lQJoPqtCYWv0NL115XM93hIH4CSRln2w52SqvmY5hqdtauB6QFCDiZNQ==} engines: {node: '>=6.9.0'} @@ -1795,8 +1827,8 @@ packages: resolution: {integrity: sha512-uLls06UVKgFG9QD4OeFYLEGteMIAa5kpTPcFL28yuCIIzsf6ZyKZMllKVOCZFhiZ5ptnwX4mtKdWCBE/uT4amg==} engines: {node: '>=6.9.0'} - '@babel/helper-plugin-utils@7.24.7': - resolution: {integrity: sha512-Rq76wjt7yz9AAc1KnlRKNAi/dMSVWgDRx43FHoJEbcYU6xOWaE2dVPwcdTukJrjxS65GITyfbvEYHvkirZ6uEg==} + '@babel/helper-simple-access@7.22.5': + resolution: {integrity: sha512-n0H99E/K+Bika3++WNL17POvo4rKWZ7lZEp1Q+fStVbUi8nxPQEBOlTmCOxW/0JsS56SKKQ+ojAe2pHKJHN35w==} engines: {node: '>=6.9.0'} '@babel/helper-simple-access@7.24.7': @@ -1823,10 +1855,18 @@ packages: resolution: {integrity: sha512-AM6TzwYqGChO45oiuPqwL2t20/HdMC1rTPAesnBCgPCSF1x3oN9MVUwQV2iyz4xqWrctwK5RNC8LV22kaQCNYg==} engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.23.5': + resolution: {integrity: sha512-85ttAOMLsr53VgXkTbkx8oA6YTfT4q7/HzXSLEYmjcSTJPMPQtvq1BD79Byep5xMUYbGRzEpDsjUf3dyp54IKw==} + engines: {node: '>=6.9.0'} + '@babel/helper-validator-option@7.24.7': resolution: {integrity: sha512-yy1/KvjhV/ZCL+SM7hBrvnZJ3ZuT9OuZgIJAGpPEToANvc3iM6iDvBnRjtElWibHU6n8/LPR/EjX9EtIEYO3pw==} engines: {node: '>=6.9.0'} + '@babel/helpers@7.23.5': + resolution: {integrity: sha512-oO7us8FzTEsG3U6ag9MfdF1iA/7Z6dz+MtFhifZk8C8o453rGJFFWUP1t+ULM9TUIAzC9uxXEiXjOiVMyd7QPg==} + engines: {node: '>=6.9.0'} + '@babel/helpers@7.24.7': resolution: {integrity: sha512-NlmJJtvcw72yRJRcnCmGvSi+3jDEg8qFu3z0AFoymmzLx5ERVWyzd9kVXr7Th9/8yIJi2Zc6av4Tqz3wFs8QWg==} engines: {node: '>=6.9.0'} @@ -1922,10 +1962,22 @@ packages: resolution: {integrity: sha512-2Yv65nlWnWlSpe3fXEyX5i7fx5kIKo4Qbcj+hMO0odwaneFjfXw5fdum+4yL20O0QiaHpia0cYQ9xpNMqrBwHg==} engines: {node: '>=6.9.0'} + '@babel/template@7.22.15': + resolution: {integrity: sha512-QPErUVm4uyJa60rkI73qneDacvdvzxshT3kksGqlGWYdOTIUOwJ7RDUL8sGqslY1uXWSL6xMFKEXDS3ox2uF0w==} + engines: {node: '>=6.9.0'} + + '@babel/template@7.24.0': + resolution: {integrity: sha512-Bkf2q8lMB0AFpX0NFEqSbx1OkTHf0f+0j82mkw+ZpzBnkk7e9Ql0891vlfgi+kHwOk8tQjiQHpqh4LaSa0fKEA==} + engines: {node: '>=6.9.0'} + '@babel/template@7.24.7': resolution: {integrity: sha512-jYqfPrU9JTF0PmPy1tLYHW4Mp4KlgxJD9l2nP9fD6yT/ICi554DmrWBAEYpIelzjHf1msDP3PxJIRt/nFNfBig==} engines: {node: '>=6.9.0'} + '@babel/traverse@7.23.5': + resolution: {integrity: sha512-czx7Xy5a6sapWWRx61m1Ke1Ra4vczu1mCTtJam5zRTBOonfdJ+S/B6HYmGYu3fJtr8GGET3si6IhgWVBhJ/m8w==} + engines: {node: '>=6.9.0'} + '@babel/traverse@7.24.7': resolution: {integrity: sha512-yb65Ed5S/QAcewNPh0nZczy9JdYXkkAbIsEo+P7BE7yO3txAY30Y/oPa3QkQ5It3xVG2kpKMg9MsdxZaO31uKA==} engines: {node: '>=6.9.0'} @@ -1938,25 +1990,22 @@ packages: resolution: {integrity: sha512-vwIVdXG+j+FOpkwqHRcBgHLYNL7XMkufrlaFvL9o6Ai9sJn9+PdyIL5qa0XzTZw084c+u9LOls53eoZWP/W5WQ==} engines: {node: '>=6.9.0'} - '@base2/pretty-print-object@1.0.1': - resolution: {integrity: sha512-4iri8i1AqYHJE2DstZYkyEprg6Pq6sKx3xn5FpySk9sNhH7qN2LLlHJCfDTZRILNwQNPD7mATWM0TBui7uC1pA==} - '@bcoe/v8-coverage@0.2.3': resolution: {integrity: sha512-0hYQ8SB4Db5zvZB4axdMHGwEaQjkZzFjQiN9LVYvIFB2nSUHW9tYpxWriPrWDASIxiaXax83REcLxuSdnGPZtw==} - '@bull-board/api@6.0.0': - resolution: {integrity: sha512-O0IsIwAOU47bPTJnqRO7RtKFQToMvwRebbuPi6M+SG1gXyiqixLg9pycnfXgSeroaT9E7QQ2PsCPW1HO8VORvw==} + '@bull-board/api@6.5.0': + resolution: {integrity: sha512-sFHxmqtbBBkQaJxHdRq2sAR0+l9TBInItXaIdBMjeBXrFW881g4aLAbO7Lno6cDPKBMauYg4TdBtRPTkjhr43w==} peerDependencies: - '@bull-board/ui': 6.0.0 + '@bull-board/ui': 6.5.0 - '@bull-board/fastify@6.0.0': - resolution: {integrity: sha512-VrKa5BdxYmXh5fJvlSPSm71b+QA9VVXHyGk6xmI/qAefUQbwd2cWJo+ppqaWSaweXa9ymJc+V4l/un0K4oomVA==} + '@bull-board/fastify@6.5.0': + resolution: {integrity: sha512-oPLqIJPkis13WMPeuephkGeP/++AB5Qw3aw0qESU1K+e1pdwzf1kYIFpOZuTe9L/MrEulKe2ZgENw6RIgC1RBw==} - '@bull-board/ui@6.0.0': - resolution: {integrity: sha512-wAFTlBTJbq5DSWxCzTV+FOyZDVwrXP+G1CQ2BpLG9o9+dpwYxUESx/VxNEDHnyPcy13gm29kB4fSRY+nkelkcQ==} + '@bull-board/ui@6.5.0': + resolution: {integrity: sha512-gIoOgKNVAnPdKBBUaBUSNS2cBJz8UYGfEuYzD/H9HIpkCHiPTUVoMO48w/D+urJoko2nW8OSkU1kf2OkZsqP0Q==} - '@bundled-es-modules/cookie@2.0.0': - resolution: {integrity: sha512-Or6YHg/kamKHpxULAdSqhGqnWFneIXu1NKvvfBBzKGwpVsYuFIQ5aBPHDnnoR3ghW1nvSkALd+EF9iMtY7Vjxw==} + '@bundled-es-modules/cookie@2.0.1': + resolution: {integrity: sha512-8o+5fRPLNbjbdGRRmJj3h6Hh1AQJf2dk3qQ/5ZFb+PXkRNiSoMGGUKlsgLfrxneb72axVJyIYji64E2+nNfYyw==} '@bundled-es-modules/statuses@1.0.1': resolution: {integrity: sha512-yn7BklA5acgcBr+7w064fGV+SGIFySjCKpqjcWgBAIfrAkY+4GQTJJHQMeT3V/sgz23VTEVV8TtOmkvJAhFVfg==} @@ -2004,8 +2053,8 @@ packages: '@cropper/utils@2.0.0-rc.2': resolution: {integrity: sha512-EEivNsyV6BtL496m4Q/IeAC6FGlyKjKIT1qMtwaxtkR+2ZlKnf9O7AdcGpClemIBA+TbwWAzp0UyIvYFtKUZ1Q==} - '@cypress/request@3.0.5': - resolution: {integrity: sha512-v+XHd9XmWbufxF1/bTaVm2yhbxY+TB4YtWRqF2zaXBlDNMkls34KiATz0AVDLavL3iB6bQk9/7n3oY1EoLSWGA==} + '@cypress/request@3.0.6': + resolution: {integrity: sha512-fi0eVdCOtKu5Ed6+E8mYxUF6ZTFJDZvHogCBelM0xVXmrDEkyM22gRArQzq1YcHPm1V47Vf/iAD+WgVdUlJCGg==} engines: {node: '>= 6'} '@cypress/xvfb@1.2.4': @@ -2021,26 +2070,14 @@ packages: '@emnapi/runtime@1.3.0': resolution: {integrity: sha512-XMBySMuNZs3DM96xcJmLW4EfGnf+uGmFNjzpehMjuX5PLB5j87ar2Zc4e3PVeZ3I5g3tYtAqskB28manlF69Zw==} - '@esbuild/aix-ppc64@0.19.11': - resolution: {integrity: sha512-FnzU0LyE3ySQk7UntJO4+qIiQgI7KoODnZg5xzXIrFJlKd2P2gwHsHY4927xj9y5PJmJSzULiUCWmv7iWnNa7g==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [aix] - '@esbuild/aix-ppc64@0.21.5': resolution: {integrity: sha512-1SDgH6ZSPTlggy1yI6+Dbkiz8xzpHJEVAlF/AM1tHPLsf5STom9rwtjE4hKAF20FfXXNTFqEYXyJNWh1GiZedQ==} engines: {node: '>=12'} cpu: [ppc64] os: [aix] - '@esbuild/aix-ppc64@0.23.0': - resolution: {integrity: sha512-3sG8Zwa5fMcA9bgqB8AfWPQ+HFke6uD3h1s3RIwUNK8EG7a4buxvuFTs3j1IMs2NXAk9F30C/FF4vxRgQCcmoQ==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [aix] - - '@esbuild/aix-ppc64@0.23.1': - resolution: {integrity: sha512-6VhYk1diRqrhBAqpJEdjASR/+WVRtfjpqKuNw11cLiaWpAT/Uu+nokB+UJnevzy/P9C/ty6AOe0dwueMrGh/iQ==} + '@esbuild/aix-ppc64@0.24.0': + resolution: {integrity: sha512-WtKdFM7ls47zkKHFVzMz8opM7LkcsIp9amDUBIAWirg70RM71WRSjdILPsY5Uv1D42ZpUfaPILDlfactHgsRkw==} engines: {node: '>=18'} cpu: [ppc64] os: [aix] @@ -2051,26 +2088,14 @@ packages: cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.19.11': - resolution: {integrity: sha512-aiu7K/5JnLj//KOnOfEZ0D90obUkRzDMyqd/wNAUQ34m4YUPVhRZpnqKV9uqDGxT7cToSDnIHsGooyIczu9T+Q==} - engines: {node: '>=12'} - cpu: [arm64] - os: [android] - '@esbuild/android-arm64@0.21.5': resolution: {integrity: sha512-c0uX9VAUBQ7dTDCjq+wdyGLowMdtR/GoC2U5IYk/7D1H1JYC0qseD7+11iMP2mRLN9RcCMRcjC4YMclCzGwS/A==} engines: {node: '>=12'} cpu: [arm64] os: [android] - '@esbuild/android-arm64@0.23.0': - resolution: {integrity: sha512-EuHFUYkAVfU4qBdyivULuu03FhJO4IJN9PGuABGrFy4vUuzk91P2d+npxHcFdpUnfYKy0PuV+n6bKIpHOB3prQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [android] - - '@esbuild/android-arm64@0.23.1': - resolution: {integrity: sha512-xw50ipykXcLstLeWH7WRdQuysJqejuAGPd30vd1i5zSyKK3WE+ijzHmLKxdiCMtH1pHz78rOg0BKSYOSB/2Khw==} + '@esbuild/android-arm64@0.24.0': + resolution: {integrity: sha512-Vsm497xFM7tTIPYK9bNTYJyF/lsP590Qc1WxJdlB6ljCbdZKU9SY8i7+Iin4kyhV/KV5J2rOKsBQbB77Ab7L/w==} engines: {node: '>=18'} cpu: [arm64] os: [android] @@ -2081,26 +2106,14 @@ packages: cpu: [arm] os: [android] - '@esbuild/android-arm@0.19.11': - resolution: {integrity: sha512-5OVapq0ClabvKvQ58Bws8+wkLCV+Rxg7tUVbo9xu034Nm536QTII4YzhaFriQ7rMrorfnFKUsArD2lqKbFY4vw==} - engines: {node: '>=12'} - cpu: [arm] - os: [android] - '@esbuild/android-arm@0.21.5': resolution: {integrity: sha512-vCPvzSjpPHEi1siZdlvAlsPxXl7WbOVUBBAowWug4rJHb68Ox8KualB+1ocNvT5fjv6wpkX6o/iEpbDrf68zcg==} engines: {node: '>=12'} cpu: [arm] os: [android] - '@esbuild/android-arm@0.23.0': - resolution: {integrity: sha512-+KuOHTKKyIKgEEqKbGTK8W7mPp+hKinbMBeEnNzjJGyFcWsfrXjSTNluJHCY1RqhxFurdD8uNXQDei7qDlR6+g==} - engines: {node: '>=18'} - cpu: [arm] - os: [android] - - '@esbuild/android-arm@0.23.1': - resolution: {integrity: sha512-uz6/tEy2IFm9RYOyvKl88zdzZfwEfKZmnX9Cj1BHjeSGNuGLuMD1kR8y5bteYmwqKm1tj8m4cb/aKEorr6fHWQ==} + '@esbuild/android-arm@0.24.0': + resolution: {integrity: sha512-arAtTPo76fJ/ICkXWetLCc9EwEHKaeya4vMrReVlEIUCAUncH7M4bhMQ+M9Vf+FFOZJdTNMXNBrWwW+OXWpSew==} engines: {node: '>=18'} cpu: [arm] os: [android] @@ -2111,26 +2124,14 @@ packages: cpu: [x64] os: [android] - '@esbuild/android-x64@0.19.11': - resolution: {integrity: sha512-eccxjlfGw43WYoY9QgB82SgGgDbibcqyDTlk3l3C0jOVHKxrjdc9CTwDUQd0vkvYg5um0OH+GpxYvp39r+IPOg==} - engines: {node: '>=12'} - cpu: [x64] - os: [android] - '@esbuild/android-x64@0.21.5': resolution: {integrity: sha512-D7aPRUUNHRBwHxzxRvp856rjUHRFW1SdQATKXH2hqA0kAZb1hKmi02OpYRacl0TxIGz/ZmXWlbZgjwWYaCakTA==} engines: {node: '>=12'} cpu: [x64] os: [android] - '@esbuild/android-x64@0.23.0': - resolution: {integrity: sha512-WRrmKidLoKDl56LsbBMhzTTBxrsVwTKdNbKDalbEZr0tcsBgCLbEtoNthOW6PX942YiYq8HzEnb4yWQMLQuipQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [android] - - '@esbuild/android-x64@0.23.1': - resolution: {integrity: sha512-nlN9B69St9BwUoB+jkyU090bru8L0NA3yFvAd7k8dNsVH8bi9a8cUAUSEcEEgTp2z3dbEDGJGfP6VUnkQnlReg==} + '@esbuild/android-x64@0.24.0': + resolution: {integrity: sha512-t8GrvnFkiIY7pa7mMgJd7p8p8qqYIz1NYiAoKc75Zyv73L3DZW++oYMSHPRarcotTKuSs6m3hTOa5CKHaS02TQ==} engines: {node: '>=18'} cpu: [x64] os: [android] @@ -2141,26 +2142,14 @@ packages: cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.19.11': - resolution: {integrity: sha512-ETp87DRWuSt9KdDVkqSoKoLFHYTrkyz2+65fj9nfXsaV3bMhTCjtQfw3y+um88vGRKRiF7erPrh/ZuIdLUIVxQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [darwin] - '@esbuild/darwin-arm64@0.21.5': resolution: {integrity: sha512-DwqXqZyuk5AiWWf3UfLiRDJ5EDd49zg6O9wclZ7kUMv2WRFr4HKjXp/5t8JZ11QbQfUS6/cRCKGwYhtNAY88kQ==} engines: {node: '>=12'} cpu: [arm64] os: [darwin] - '@esbuild/darwin-arm64@0.23.0': - resolution: {integrity: sha512-YLntie/IdS31H54Ogdn+v50NuoWF5BDkEUFpiOChVa9UnKpftgwzZRrI4J132ETIi+D8n6xh9IviFV3eXdxfow==} - engines: {node: '>=18'} - cpu: [arm64] - os: [darwin] - - '@esbuild/darwin-arm64@0.23.1': - resolution: {integrity: sha512-YsS2e3Wtgnw7Wq53XXBLcV6JhRsEq8hkfg91ESVadIrzr9wO6jJDMZnCQbHm1Guc5t/CdDiFSSfWP58FNuvT3Q==} + '@esbuild/darwin-arm64@0.24.0': + resolution: {integrity: sha512-CKyDpRbK1hXwv79soeTJNHb5EiG6ct3efd/FTPdzOWdbZZfGhpbcqIpiD0+vwmpu0wTIL97ZRPZu8vUt46nBSw==} engines: {node: '>=18'} cpu: [arm64] os: [darwin] @@ -2171,26 +2160,14 @@ packages: cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.19.11': - resolution: {integrity: sha512-fkFUiS6IUK9WYUO/+22omwetaSNl5/A8giXvQlcinLIjVkxwTLSktbF5f/kJMftM2MJp9+fXqZ5ezS7+SALp4g==} - engines: {node: '>=12'} - cpu: [x64] - os: [darwin] - '@esbuild/darwin-x64@0.21.5': resolution: {integrity: sha512-se/JjF8NlmKVG4kNIuyWMV/22ZaerB+qaSi5MdrXtd6R08kvs2qCN4C09miupktDitvh8jRFflwGFBQcxZRjbw==} engines: {node: '>=12'} cpu: [x64] os: [darwin] - '@esbuild/darwin-x64@0.23.0': - resolution: {integrity: sha512-IMQ6eme4AfznElesHUPDZ+teuGwoRmVuuixu7sv92ZkdQcPbsNHzutd+rAfaBKo8YK3IrBEi9SLLKWJdEvJniQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [darwin] - - '@esbuild/darwin-x64@0.23.1': - resolution: {integrity: sha512-aClqdgTDVPSEGgoCS8QDG37Gu8yc9lTHNAQlsztQ6ENetKEO//b8y31MMu2ZaPbn4kVsIABzVLXYLhCGekGDqw==} + '@esbuild/darwin-x64@0.24.0': + resolution: {integrity: sha512-rgtz6flkVkh58od4PwTRqxbKH9cOjaXCMZgWD905JOzjFKW+7EiUObfd/Kav+A6Gyud6WZk9w+xu6QLytdi2OA==} engines: {node: '>=18'} cpu: [x64] os: [darwin] @@ -2201,26 +2178,14 @@ packages: cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.19.11': - resolution: {integrity: sha512-lhoSp5K6bxKRNdXUtHoNc5HhbXVCS8V0iZmDvyWvYq9S5WSfTIHU2UGjcGt7UeS6iEYp9eeymIl5mJBn0yiuxA==} - engines: {node: '>=12'} - cpu: [arm64] - os: [freebsd] - '@esbuild/freebsd-arm64@0.21.5': resolution: {integrity: sha512-5JcRxxRDUJLX8JXp/wcBCy3pENnCgBR9bN6JsY4OmhfUtIHe3ZW0mawA7+RDAcMLrMIZaf03NlQiX9DGyB8h4g==} engines: {node: '>=12'} cpu: [arm64] os: [freebsd] - '@esbuild/freebsd-arm64@0.23.0': - resolution: {integrity: sha512-0muYWCng5vqaxobq6LB3YNtevDFSAZGlgtLoAc81PjUfiFz36n4KMpwhtAd4he8ToSI3TGyuhyx5xmiWNYZFyw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [freebsd] - - '@esbuild/freebsd-arm64@0.23.1': - resolution: {integrity: sha512-h1k6yS8/pN/NHlMl5+v4XPfikhJulk4G+tKGFIOwURBSFzE8bixw1ebjluLOjfwtLqY0kewfjLSrO6tN2MgIhA==} + '@esbuild/freebsd-arm64@0.24.0': + resolution: {integrity: sha512-6Mtdq5nHggwfDNLAHkPlyLBpE5L6hwsuXZX8XNmHno9JuL2+bg2BX5tRkwjyfn6sKbxZTq68suOjgWqCicvPXA==} engines: {node: '>=18'} cpu: [arm64] os: [freebsd] @@ -2231,26 +2196,14 @@ packages: cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.19.11': - resolution: {integrity: sha512-JkUqn44AffGXitVI6/AbQdoYAq0TEullFdqcMY/PCUZ36xJ9ZJRtQabzMA+Vi7r78+25ZIBosLTOKnUXBSi1Kw==} - engines: {node: '>=12'} - cpu: [x64] - os: [freebsd] - '@esbuild/freebsd-x64@0.21.5': resolution: {integrity: sha512-J95kNBj1zkbMXtHVH29bBriQygMXqoVQOQYA+ISs0/2l3T9/kj42ow2mpqerRBxDJnmkUDCaQT/dfNXWX/ZZCQ==} engines: {node: '>=12'} cpu: [x64] os: [freebsd] - '@esbuild/freebsd-x64@0.23.0': - resolution: {integrity: sha512-XKDVu8IsD0/q3foBzsXGt/KjD/yTKBCIwOHE1XwiXmrRwrX6Hbnd5Eqn/WvDekddK21tfszBSrE/WMaZh+1buQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [freebsd] - - '@esbuild/freebsd-x64@0.23.1': - resolution: {integrity: sha512-lK1eJeyk1ZX8UklqFd/3A60UuZ/6UVfGT2LuGo3Wp4/z7eRTRYY+0xOu2kpClP+vMTi9wKOfXi2vjUpO1Ro76g==} + '@esbuild/freebsd-x64@0.24.0': + resolution: {integrity: sha512-D3H+xh3/zphoX8ck4S2RxKR6gHlHDXXzOf6f/9dbFt/NRBDIE33+cVa49Kil4WUjxMGW0ZIYBYtaGCa2+OsQwQ==} engines: {node: '>=18'} cpu: [x64] os: [freebsd] @@ -2261,26 +2214,14 @@ packages: cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.19.11': - resolution: {integrity: sha512-LneLg3ypEeveBSMuoa0kwMpCGmpu8XQUh+mL8XXwoYZ6Be2qBnVtcDI5azSvh7vioMDhoJFZzp9GWp9IWpYoUg==} - engines: {node: '>=12'} - cpu: [arm64] - os: [linux] - '@esbuild/linux-arm64@0.21.5': resolution: {integrity: sha512-ibKvmyYzKsBeX8d8I7MH/TMfWDXBF3db4qM6sy+7re0YXya+K1cem3on9XgdT2EQGMu4hQyZhan7TeQ8XkGp4Q==} engines: {node: '>=12'} cpu: [arm64] os: [linux] - '@esbuild/linux-arm64@0.23.0': - resolution: {integrity: sha512-j1t5iG8jE7BhonbsEg5d9qOYcVZv/Rv6tghaXM/Ug9xahM0nX/H2gfu6X6z11QRTMT6+aywOMA8TDkhPo8aCGw==} - engines: {node: '>=18'} - cpu: [arm64] - os: [linux] - - '@esbuild/linux-arm64@0.23.1': - resolution: {integrity: sha512-/93bf2yxencYDnItMYV/v116zff6UyTjo4EtEQjUBeGiVpMmffDNUyD9UN2zV+V3LRV3/on4xdZ26NKzn6754g==} + '@esbuild/linux-arm64@0.24.0': + resolution: {integrity: sha512-TDijPXTOeE3eaMkRYpcy3LarIg13dS9wWHRdwYRnzlwlA370rNdZqbcp0WTyyV/k2zSxfko52+C7jU5F9Tfj1g==} engines: {node: '>=18'} cpu: [arm64] os: [linux] @@ -2291,26 +2232,14 @@ packages: cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.19.11': - resolution: {integrity: sha512-3CRkr9+vCV2XJbjwgzjPtO8T0SZUmRZla+UL1jw+XqHZPkPgZiyWvbDvl9rqAN8Zl7qJF0O/9ycMtjU67HN9/Q==} - engines: {node: '>=12'} - cpu: [arm] - os: [linux] - '@esbuild/linux-arm@0.21.5': resolution: {integrity: sha512-bPb5AHZtbeNGjCKVZ9UGqGwo8EUu4cLq68E95A53KlxAPRmUyYv2D6F0uUI65XisGOL1hBP5mTronbgo+0bFcA==} engines: {node: '>=12'} cpu: [arm] os: [linux] - '@esbuild/linux-arm@0.23.0': - resolution: {integrity: sha512-SEELSTEtOFu5LPykzA395Mc+54RMg1EUgXP+iw2SJ72+ooMwVsgfuwXo5Fn0wXNgWZsTVHwY2cg4Vi/bOD88qw==} - engines: {node: '>=18'} - cpu: [arm] - os: [linux] - - '@esbuild/linux-arm@0.23.1': - resolution: {integrity: sha512-CXXkzgn+dXAPs3WBwE+Kvnrf4WECwBdfjfeYHpMeVxWE0EceB6vhWGShs6wi0IYEqMSIzdOF1XjQ/Mkm5d7ZdQ==} + '@esbuild/linux-arm@0.24.0': + resolution: {integrity: sha512-gJKIi2IjRo5G6Glxb8d3DzYXlxdEj2NlkixPsqePSZMhLudqPhtZ4BUrpIuTjJYXxvF9njql+vRjB2oaC9XpBw==} engines: {node: '>=18'} cpu: [arm] os: [linux] @@ -2321,26 +2250,14 @@ packages: cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.19.11': - resolution: {integrity: sha512-caHy++CsD8Bgq2V5CodbJjFPEiDPq8JJmBdeyZ8GWVQMjRD0sU548nNdwPNvKjVpamYYVL40AORekgfIubwHoA==} - engines: {node: '>=12'} - cpu: [ia32] - os: [linux] - '@esbuild/linux-ia32@0.21.5': resolution: {integrity: sha512-YvjXDqLRqPDl2dvRODYmmhz4rPeVKYvppfGYKSNGdyZkA01046pLWyRKKI3ax8fbJoK5QbxblURkwK/MWY18Tg==} engines: {node: '>=12'} cpu: [ia32] os: [linux] - '@esbuild/linux-ia32@0.23.0': - resolution: {integrity: sha512-P7O5Tkh2NbgIm2R6x1zGJJsnacDzTFcRWZyTTMgFdVit6E98LTxO+v8LCCLWRvPrjdzXHx9FEOA8oAZPyApWUA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [linux] - - '@esbuild/linux-ia32@0.23.1': - resolution: {integrity: sha512-VTN4EuOHwXEkXzX5nTvVY4s7E/Krz7COC8xkftbbKRYAl96vPiUssGkeMELQMOnLOJ8k3BY1+ZY52tttZnHcXQ==} + '@esbuild/linux-ia32@0.24.0': + resolution: {integrity: sha512-K40ip1LAcA0byL05TbCQ4yJ4swvnbzHscRmUilrmP9Am7//0UjPreh4lpYzvThT2Quw66MhjG//20mrufm40mA==} engines: {node: '>=18'} cpu: [ia32] os: [linux] @@ -2351,26 +2268,14 @@ packages: cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.19.11': - resolution: {integrity: sha512-ppZSSLVpPrwHccvC6nQVZaSHlFsvCQyjnvirnVjbKSHuE5N24Yl8F3UwYUUR1UEPaFObGD2tSvVKbvR+uT1Nrg==} - engines: {node: '>=12'} - cpu: [loong64] - os: [linux] - '@esbuild/linux-loong64@0.21.5': resolution: {integrity: sha512-uHf1BmMG8qEvzdrzAqg2SIG/02+4/DHB6a9Kbya0XDvwDEKCoC8ZRWI5JJvNdUjtciBGFQ5PuBlpEOXQj+JQSg==} engines: {node: '>=12'} cpu: [loong64] os: [linux] - '@esbuild/linux-loong64@0.23.0': - resolution: {integrity: sha512-InQwepswq6urikQiIC/kkx412fqUZudBO4SYKu0N+tGhXRWUqAx+Q+341tFV6QdBifpjYgUndV1hhMq3WeJi7A==} - engines: {node: '>=18'} - cpu: [loong64] - os: [linux] - - '@esbuild/linux-loong64@0.23.1': - resolution: {integrity: sha512-Vx09LzEoBa5zDnieH8LSMRToj7ir/Jeq0Gu6qJ/1GcBq9GkfoEAoXvLiW1U9J1qE/Y/Oyaq33w5p2ZWrNNHNEw==} + '@esbuild/linux-loong64@0.24.0': + resolution: {integrity: sha512-0mswrYP/9ai+CU0BzBfPMZ8RVm3RGAN/lmOMgW4aFUSOQBjA31UP8Mr6DDhWSuMwj7jaWOT0p0WoZ6jeHhrD7g==} engines: {node: '>=18'} cpu: [loong64] os: [linux] @@ -2381,26 +2286,14 @@ packages: cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.19.11': - resolution: {integrity: sha512-B5x9j0OgjG+v1dF2DkH34lr+7Gmv0kzX6/V0afF41FkPMMqaQ77pH7CrhWeR22aEeHKaeZVtZ6yFwlxOKPVFyg==} - engines: {node: '>=12'} - cpu: [mips64el] - os: [linux] - '@esbuild/linux-mips64el@0.21.5': resolution: {integrity: sha512-IajOmO+KJK23bj52dFSNCMsz1QP1DqM6cwLUv3W1QwyxkyIWecfafnI555fvSGqEKwjMXVLokcV5ygHW5b3Jbg==} engines: {node: '>=12'} cpu: [mips64el] os: [linux] - '@esbuild/linux-mips64el@0.23.0': - resolution: {integrity: sha512-J9rflLtqdYrxHv2FqXE2i1ELgNjT+JFURt/uDMoPQLcjWQA5wDKgQA4t/dTqGa88ZVECKaD0TctwsUfHbVoi4w==} - engines: {node: '>=18'} - cpu: [mips64el] - os: [linux] - - '@esbuild/linux-mips64el@0.23.1': - resolution: {integrity: sha512-nrFzzMQ7W4WRLNUOU5dlWAqa6yVeI0P78WKGUo7lg2HShq/yx+UYkeNSE0SSfSure0SqgnsxPvmAUu/vu0E+3Q==} + '@esbuild/linux-mips64el@0.24.0': + resolution: {integrity: sha512-hIKvXm0/3w/5+RDtCJeXqMZGkI2s4oMUGj3/jM0QzhgIASWrGO5/RlzAzm5nNh/awHE0A19h/CvHQe6FaBNrRA==} engines: {node: '>=18'} cpu: [mips64el] os: [linux] @@ -2411,26 +2304,14 @@ packages: cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.19.11': - resolution: {integrity: sha512-MHrZYLeCG8vXblMetWyttkdVRjQlQUb/oMgBNurVEnhj4YWOr4G5lmBfZjHYQHHN0g6yDmCAQRR8MUHldvvRDA==} - engines: {node: '>=12'} - cpu: [ppc64] - os: [linux] - '@esbuild/linux-ppc64@0.21.5': resolution: {integrity: sha512-1hHV/Z4OEfMwpLO8rp7CvlhBDnjsC3CttJXIhBi+5Aj5r+MBvy4egg7wCbe//hSsT+RvDAG7s81tAvpL2XAE4w==} engines: {node: '>=12'} cpu: [ppc64] os: [linux] - '@esbuild/linux-ppc64@0.23.0': - resolution: {integrity: sha512-cShCXtEOVc5GxU0fM+dsFD10qZ5UpcQ8AM22bYj0u/yaAykWnqXJDpd77ublcX6vdDsWLuweeuSNZk4yUxZwtw==} - engines: {node: '>=18'} - cpu: [ppc64] - os: [linux] - - '@esbuild/linux-ppc64@0.23.1': - resolution: {integrity: sha512-dKN8fgVqd0vUIjxuJI6P/9SSSe/mB9rvA98CSH2sJnlZ/OCZWO1DJvxj8jvKTfYUdGfcq2dDxoKaC6bHuTlgcw==} + '@esbuild/linux-ppc64@0.24.0': + resolution: {integrity: sha512-HcZh5BNq0aC52UoocJxaKORfFODWXZxtBaaZNuN3PUX3MoDsChsZqopzi5UupRhPHSEHotoiptqikjN/B77mYQ==} engines: {node: '>=18'} cpu: [ppc64] os: [linux] @@ -2441,26 +2322,14 @@ packages: cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.19.11': - resolution: {integrity: sha512-f3DY++t94uVg141dozDu4CCUkYW+09rWtaWfnb3bqe4w5NqmZd6nPVBm+qbz7WaHZCoqXqHz5p6CM6qv3qnSSQ==} - engines: {node: '>=12'} - cpu: [riscv64] - os: [linux] - '@esbuild/linux-riscv64@0.21.5': resolution: {integrity: sha512-2HdXDMd9GMgTGrPWnJzP2ALSokE/0O5HhTUvWIbD3YdjME8JwvSCnNGBnTThKGEB91OZhzrJ4qIIxk/SBmyDDA==} engines: {node: '>=12'} cpu: [riscv64] os: [linux] - '@esbuild/linux-riscv64@0.23.0': - resolution: {integrity: sha512-HEtaN7Y5UB4tZPeQmgz/UhzoEyYftbMXrBCUjINGjh3uil+rB/QzzpMshz3cNUxqXN7Vr93zzVtpIDL99t9aRw==} - engines: {node: '>=18'} - cpu: [riscv64] - os: [linux] - - '@esbuild/linux-riscv64@0.23.1': - resolution: {integrity: sha512-5AV4Pzp80fhHL83JM6LoA6pTQVWgB1HovMBsLQ9OZWLDqVY8MVobBXNSmAJi//Csh6tcY7e7Lny2Hg1tElMjIA==} + '@esbuild/linux-riscv64@0.24.0': + resolution: {integrity: sha512-bEh7dMn/h3QxeR2KTy1DUszQjUrIHPZKyO6aN1X4BCnhfYhuQqedHaa5MxSQA/06j3GpiIlFGSsy1c7Gf9padw==} engines: {node: '>=18'} cpu: [riscv64] os: [linux] @@ -2471,26 +2340,14 @@ packages: cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.19.11': - resolution: {integrity: sha512-A5xdUoyWJHMMlcSMcPGVLzYzpcY8QP1RtYzX5/bS4dvjBGVxdhuiYyFwp7z74ocV7WDc0n1harxmpq2ePOjI0Q==} - engines: {node: '>=12'} - cpu: [s390x] - os: [linux] - '@esbuild/linux-s390x@0.21.5': resolution: {integrity: sha512-zus5sxzqBJD3eXxwvjN1yQkRepANgxE9lgOW2qLnmr8ikMTphkjgXu1HR01K4FJg8h1kEEDAqDcZQtbrRnB41A==} engines: {node: '>=12'} cpu: [s390x] os: [linux] - '@esbuild/linux-s390x@0.23.0': - resolution: {integrity: sha512-WDi3+NVAuyjg/Wxi+o5KPqRbZY0QhI9TjrEEm+8dmpY9Xir8+HE/HNx2JoLckhKbFopW0RdO2D72w8trZOV+Wg==} - engines: {node: '>=18'} - cpu: [s390x] - os: [linux] - - '@esbuild/linux-s390x@0.23.1': - resolution: {integrity: sha512-9ygs73tuFCe6f6m/Tb+9LtYxWR4c9yg7zjt2cYkjDbDpV/xVn+68cQxMXCjUpYwEkze2RcU/rMnfIXNRFmSoDw==} + '@esbuild/linux-s390x@0.24.0': + resolution: {integrity: sha512-ZcQ6+qRkw1UcZGPyrCiHHkmBaj9SiCD8Oqd556HldP+QlpUIe2Wgn3ehQGVoPOvZvtHm8HPx+bH20c9pvbkX3g==} engines: {node: '>=18'} cpu: [s390x] os: [linux] @@ -2501,26 +2358,14 @@ packages: cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.19.11': - resolution: {integrity: sha512-grbyMlVCvJSfxFQUndw5mCtWs5LO1gUlwP4CDi4iJBbVpZcqLVT29FxgGuBJGSzyOxotFG4LoO5X+M1350zmPA==} - engines: {node: '>=12'} - cpu: [x64] - os: [linux] - '@esbuild/linux-x64@0.21.5': resolution: {integrity: sha512-1rYdTpyv03iycF1+BhzrzQJCdOuAOtaqHTWJZCWvijKD2N5Xu0TtVC8/+1faWqcP9iBCWOmjmhoH94dH82BxPQ==} engines: {node: '>=12'} cpu: [x64] os: [linux] - '@esbuild/linux-x64@0.23.0': - resolution: {integrity: sha512-a3pMQhUEJkITgAw6e0bWA+F+vFtCciMjW/LPtoj99MhVt+Mfb6bbL9hu2wmTZgNd994qTAEw+U/r6k3qHWWaOQ==} - engines: {node: '>=18'} - cpu: [x64] - os: [linux] - - '@esbuild/linux-x64@0.23.1': - resolution: {integrity: sha512-EV6+ovTsEXCPAp58g2dD68LxoP/wK5pRvgy0J/HxPGB009omFPv3Yet0HiaqvrIrgPTBuC6wCH1LTOY91EO5hQ==} + '@esbuild/linux-x64@0.24.0': + resolution: {integrity: sha512-vbutsFqQ+foy3wSSbmjBXXIJ6PL3scghJoM8zCL142cGaZKAdCZHyf+Bpu/MmX9zT9Q0zFBVKb36Ma5Fzfa8xA==} engines: {node: '>=18'} cpu: [x64] os: [linux] @@ -2531,38 +2376,20 @@ packages: cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.19.11': - resolution: {integrity: sha512-13jvrQZJc3P230OhU8xgwUnDeuC/9egsjTkXN49b3GcS5BKvJqZn86aGM8W9pd14Kd+u7HuFBMVtrNGhh6fHEQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [netbsd] - '@esbuild/netbsd-x64@0.21.5': resolution: {integrity: sha512-Woi2MXzXjMULccIwMnLciyZH4nCIMpWQAs049KEeMvOcNADVxo0UBIQPfSmxB3CWKedngg7sWZdLvLczpe0tLg==} engines: {node: '>=12'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.0': - resolution: {integrity: sha512-cRK+YDem7lFTs2Q5nEv/HHc4LnrfBCbH5+JHu6wm2eP+d8OZNoSMYgPZJq78vqQ9g+9+nMuIsAO7skzphRXHyw==} + '@esbuild/netbsd-x64@0.24.0': + resolution: {integrity: sha512-hjQ0R/ulkO8fCYFsG0FZoH+pWgTTDreqpqY7UnQntnaKv95uP5iW3+dChxnx7C3trQQU40S+OgWhUVwCjVFLvg==} engines: {node: '>=18'} cpu: [x64] os: [netbsd] - '@esbuild/netbsd-x64@0.23.1': - resolution: {integrity: sha512-aevEkCNu7KlPRpYLjwmdcuNz6bDFiE7Z8XC4CPqExjTvrHugh28QzUXVOZtiYghciKUacNktqxdpymplil1beA==} - engines: {node: '>=18'} - cpu: [x64] - os: [netbsd] - - '@esbuild/openbsd-arm64@0.23.0': - resolution: {integrity: sha512-suXjq53gERueVWu0OKxzWqk7NxiUWSUlrxoZK7usiF50C6ipColGR5qie2496iKGYNLhDZkPxBI3erbnYkU0rQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [openbsd] - - '@esbuild/openbsd-arm64@0.23.1': - resolution: {integrity: sha512-3x37szhLexNA4bXhLrCC/LImN/YtWis6WXr1VESlfVtVeoFJBRINPJ3f0a/6LV8zpikqoUg4hyXw0sFBt5Cr+Q==} + '@esbuild/openbsd-arm64@0.24.0': + resolution: {integrity: sha512-MD9uzzkPQbYehwcN583yx3Tu5M8EIoTD+tUgKF982WYL9Pf5rKy9ltgD0eUgs8pvKnmizxjXZyLt0z6DC3rRXg==} engines: {node: '>=18'} cpu: [arm64] os: [openbsd] @@ -2573,26 +2400,14 @@ packages: cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.19.11': - resolution: {integrity: sha512-ysyOGZuTp6SNKPE11INDUeFVVQFrhcNDVUgSQVDzqsqX38DjhPEPATpid04LCoUr2WXhQTEZ8ct/EgJCUDpyNw==} - engines: {node: '>=12'} - cpu: [x64] - os: [openbsd] - '@esbuild/openbsd-x64@0.21.5': resolution: {integrity: sha512-HLNNw99xsvx12lFBUwoT8EVCsSvRNDVxNpjZ7bPn947b8gJPzeHWyNVhFsaerc0n3TsbOINvRP2byTZ5LKezow==} engines: {node: '>=12'} cpu: [x64] os: [openbsd] - '@esbuild/openbsd-x64@0.23.0': - resolution: {integrity: sha512-6p3nHpby0DM/v15IFKMjAaayFhqnXV52aEmv1whZHX56pdkK+MEaLoQWj+H42ssFarP1PcomVhbsR4pkz09qBg==} - engines: {node: '>=18'} - cpu: [x64] - os: [openbsd] - - '@esbuild/openbsd-x64@0.23.1': - resolution: {integrity: sha512-aY2gMmKmPhxfU+0EdnN+XNtGbjfQgwZj43k8G3fyrDM/UdZww6xrWxmDkuz2eCZchqVeABjV5BpildOrUbBTqA==} + '@esbuild/openbsd-x64@0.24.0': + resolution: {integrity: sha512-4ir0aY1NGUhIC1hdoCzr1+5b43mw99uNwVzhIq1OY3QcEwPDO3B7WNXBzaKY5Nsf1+N11i1eOfFcq+D/gOS15Q==} engines: {node: '>=18'} cpu: [x64] os: [openbsd] @@ -2603,26 +2418,14 @@ packages: cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.19.11': - resolution: {integrity: sha512-Hf+Sad9nVwvtxy4DXCZQqLpgmRTQqyFyhT3bZ4F2XlJCjxGmRFF0Shwn9rzhOYRB61w9VMXUkxlBy56dk9JJiQ==} - engines: {node: '>=12'} - cpu: [x64] - os: [sunos] - '@esbuild/sunos-x64@0.21.5': resolution: {integrity: sha512-6+gjmFpfy0BHU5Tpptkuh8+uw3mnrvgs+dSPQXQOv3ekbordwnzTVEb4qnIvQcYXq6gzkyTnoZ9dZG+D4garKg==} engines: {node: '>=12'} cpu: [x64] os: [sunos] - '@esbuild/sunos-x64@0.23.0': - resolution: {integrity: sha512-BFelBGfrBwk6LVrmFzCq1u1dZbG4zy/Kp93w2+y83Q5UGYF1d8sCzeLI9NXjKyujjBBniQa8R8PzLFAUrSM9OA==} - engines: {node: '>=18'} - cpu: [x64] - os: [sunos] - - '@esbuild/sunos-x64@0.23.1': - resolution: {integrity: sha512-RBRT2gqEl0IKQABT4XTj78tpk9v7ehp+mazn2HbUeZl1YMdaGAQqhapjGTCe7uw7y0frDi4gS0uHzhvpFuI1sA==} + '@esbuild/sunos-x64@0.24.0': + resolution: {integrity: sha512-jVzdzsbM5xrotH+W5f1s+JtUy1UWgjU0Cf4wMvffTB8m6wP5/kx0KiaLHlbJO+dMgtxKV8RQ/JvtlFcdZ1zCPA==} engines: {node: '>=18'} cpu: [x64] os: [sunos] @@ -2633,26 +2436,14 @@ packages: cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.19.11': - resolution: {integrity: sha512-0P58Sbi0LctOMOQbpEOvOL44Ne0sqbS0XWHMvvrg6NE5jQ1xguCSSw9jQeUk2lfrXYsKDdOe6K+oZiwKPilYPQ==} - engines: {node: '>=12'} - cpu: [arm64] - os: [win32] - '@esbuild/win32-arm64@0.21.5': resolution: {integrity: sha512-Z0gOTd75VvXqyq7nsl93zwahcTROgqvuAcYDUr+vOv8uHhNSKROyU961kgtCD1e95IqPKSQKH7tBTslnS3tA8A==} engines: {node: '>=12'} cpu: [arm64] os: [win32] - '@esbuild/win32-arm64@0.23.0': - resolution: {integrity: sha512-lY6AC8p4Cnb7xYHuIxQ6iYPe6MfO2CC43XXKo9nBXDb35krYt7KGhQnOkRGar5psxYkircpCqfbNDB4uJbS2jQ==} - engines: {node: '>=18'} - cpu: [arm64] - os: [win32] - - '@esbuild/win32-arm64@0.23.1': - resolution: {integrity: sha512-4O+gPR5rEBe2FpKOVyiJ7wNDPA8nGzDuJ6gN4okSA1gEOYZ67N8JPk58tkWtdtPeLz7lBnY6I5L3jdsr3S+A6A==} + '@esbuild/win32-arm64@0.24.0': + resolution: {integrity: sha512-iKc8GAslzRpBytO2/aN3d2yb2z8XTVfNV0PjGlCxKo5SgWmNXx82I/Q3aG1tFfS+A2igVCY97TJ8tnYwpUWLCA==} engines: {node: '>=18'} cpu: [arm64] os: [win32] @@ -2663,26 +2454,14 @@ packages: cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.19.11': - resolution: {integrity: sha512-6YOrWS+sDJDmshdBIQU+Uoyh7pQKrdykdefC1avn76ss5c+RN6gut3LZA4E2cH5xUEp5/cA0+YxRaVtRAb0xBg==} - engines: {node: '>=12'} - cpu: [ia32] - os: [win32] - '@esbuild/win32-ia32@0.21.5': resolution: {integrity: sha512-SWXFF1CL2RVNMaVs+BBClwtfZSvDgtL//G/smwAc5oVK/UPu2Gu9tIaRgFmYFFKrmg3SyAjSrElf0TiJ1v8fYA==} engines: {node: '>=12'} cpu: [ia32] os: [win32] - '@esbuild/win32-ia32@0.23.0': - resolution: {integrity: sha512-7L1bHlOTcO4ByvI7OXVI5pNN6HSu6pUQq9yodga8izeuB1KcT2UkHaH6118QJwopExPn0rMHIseCTx1CRo/uNA==} - engines: {node: '>=18'} - cpu: [ia32] - os: [win32] - - '@esbuild/win32-ia32@0.23.1': - resolution: {integrity: sha512-BcaL0Vn6QwCwre3Y717nVHZbAa4UBEigzFm6VdsVdT/MbZ38xoj1X9HPkZhbmaBGUD1W8vxAfffbDe8bA6AKnQ==} + '@esbuild/win32-ia32@0.24.0': + resolution: {integrity: sha512-vQW36KZolfIudCcTnaTpmLQ24Ha1RjygBo39/aLkM2kmjkWmZGEJ5Gn9l5/7tzXA42QGIoWbICfg6KLLkIw6yw==} engines: {node: '>=18'} cpu: [ia32] os: [win32] @@ -2693,26 +2472,14 @@ packages: cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.19.11': - resolution: {integrity: sha512-vfkhltrjCAb603XaFhqhAF4LGDi2M4OrCRrFusyQ+iTLQ/o60QQXxc9cZC/FFpihBI9N1Grn6SMKVJ4KP7Fuiw==} - engines: {node: '>=12'} - cpu: [x64] - os: [win32] - '@esbuild/win32-x64@0.21.5': resolution: {integrity: sha512-tQd/1efJuzPC6rCFwEvLtci/xNFcTZknmXs98FYDfGE4wP9ClFV98nyKrzJKVPMhdDnjzLhdUyMX4PsQAPjwIw==} engines: {node: '>=12'} cpu: [x64] os: [win32] - '@esbuild/win32-x64@0.23.0': - resolution: {integrity: sha512-Arm+WgUFLUATuoxCJcahGuk6Yj9Pzxd6l11Zb/2aAuv5kWWvvfhLFo2fni4uSK5vzlUdCGZ/BdV5tH8klj8p8g==} - engines: {node: '>=18'} - cpu: [x64] - os: [win32] - - '@esbuild/win32-x64@0.23.1': - resolution: {integrity: sha512-BHpFFeslkWrXWyUPnbKm+xYYVYruCinGcftSBaa8zoF9hZO4BcSCFUvHVTtzpIY6YzUnYtuEhZ+C9iEXjxnasg==} + '@esbuild/win32-x64@0.24.0': + resolution: {integrity: sha512-7IAFPrjSQIJrGsK6flwg7NFmwBoSTyF3rl7If0hNUFQU4ilTsEPL6GuMuU9BfIWVVGuRnuIidkSMC+c0Otu8IA==} engines: {node: '>=18'} cpu: [x64] os: [win32] @@ -2727,6 +2494,10 @@ packages: resolution: {integrity: sha512-G/M/tIiMrTAxEWRfLfQJMmGNX28IxBg4PBz8XqQhqUHLFI6TL2htpIB1iQCj144V5ee/JaKyT9/WZ0MGZWfA7A==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.12.1': + resolution: {integrity: sha512-CCZCDJuduB9OUkFkY2IgppNZMi2lBQgD2qzwXkEia16cge2pijY/aXi96CJMquDMn3nJdlPV1A5KrJEXwfLNzQ==} + engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} + '@eslint-community/regexpp@4.6.2': resolution: {integrity: sha512-pPTNuaAG3QMH+buKyBIGJs3g/S5y0caxw0ygM3YyE6yJFySwiGGSzA+mM3KJ8QQvzeLh3blwgSonkFjgQdxzMw==} engines: {node: ^12.0.0 || ^14.0.0 || >=16.0.0} @@ -2735,8 +2506,12 @@ packages: resolution: {integrity: sha512-lpHyRyplhGPL5mGEh6M9O5nnKk0Gz4bFI+Zu6tKlPpDUN7XshWvH9C/px4UVm87IAANE0W81CEsNGbS1KlzXpA==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} - '@eslint/config-array@0.17.1': - resolution: {integrity: sha512-BlYOpej8AQ8Ev9xVqroV7a02JK3SkBAaN9GfMMH9W6Ch8FlQlkjGw4Ir7+FgYwfirivAf4t+GtzuAxqfukmISA==} + '@eslint/config-array@0.18.0': + resolution: {integrity: sha512-fTxvnS1sRMu3+JjXwJG0j/i4RT9u4qJ+lqS/yCGap4lH4zZGzQ7tu+xZqQmcMZq5OBZDL4QRxQzRjkWcGt8IVw==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + + '@eslint/core@0.7.0': + resolution: {integrity: sha512-xp5Jirz5DyPYlPiKat8jaq0EmYvDXKKpzTbxXMpT9eqlRJkRKIz9AGMdlvYjih+im+QlhWrpvVjl8IPC/lHlUw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/eslintrc@2.1.4': @@ -2751,19 +2526,23 @@ packages: resolution: {integrity: sha512-Ys+3g2TaW7gADOJzPt83SJtCDhMjndcDMFVQ/Tj9iA1BfJzFKD9mAUXT3OenpuPHbI6P/myECxRJrofUsDx/5g==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - '@eslint/js@9.8.0': - resolution: {integrity: sha512-MfluB7EUfxXtv3i/++oh89uzAr4PDI4nn201hsp+qaXqsjAWzinlZEHEfPgAX4doIlKvPG/i0A9dpKxOLII8yA==} + '@eslint/js@9.14.0': + resolution: {integrity: sha512-pFoEtFWCPyDOl+C6Ift+wC7Ro89otjigCf5vcuWqWgqNSQbRrpjSvdeE6ofLz4dHmyxD5f7gIdGT4+p36L6Twg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} '@eslint/object-schema@2.1.4': resolution: {integrity: sha512-BsWiH1yFGjXXS2yvrf5LyuoSIIbPrGUWob917o+BTKuZ7qJdxX8aJLRxs1fS9n6r7vESrq1OUqb68dANcFXuQQ==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@eslint/plugin-kit@0.2.3': + resolution: {integrity: sha512-2b/g5hRmpbb1o4GnTZax9N9m0FXzz9OV42ZzI4rDDMDuHUqigAiQCEWChBWCY4ztAGVRjoWT19v0yMmc5/L5kA==} + engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} + '@fastify/accept-negotiator@2.0.0': resolution: {integrity: sha512-/Sce/kBzuTxIq5tJh85nVNOq9wKD8s+viIgX0fFMDBdw95gnpf53qmF1oBgJym3cPFliWUuSloVg/1w/rH0FcQ==} - '@fastify/accepts@5.0.0': - resolution: {integrity: sha512-5wpgycrn+DXPkATGqUbXY9tyqLNgxo9S8f0EHUyIWvUacor2cXa3liYZggsqoyMXgpIqUbGLPBl+dN2hRcU9jQ==} + '@fastify/accepts@5.0.1': + resolution: {integrity: sha512-8ji2MGTbceSnAXKYx/U9iWt6Fmf0zJovh0meO5rpwYS/vy0Z3QIR2J/hKmbcTpYfMu5NUliNpsAtMavmzBQhmA==} '@fastify/ajv-compiler@4.0.1': resolution: {integrity: sha512-DxrBdgsjNLP0YM6W5Hd6/Fmj43S8zMKiFJYgi+Ri3htTGAowPVG/tG1wpnWLMjufEnehRivUCKZ1pLDIoZdTuw==} @@ -2779,11 +2558,11 @@ packages: '@fastify/busboy@3.0.0': resolution: {integrity: sha512-83rnH2nCvclWaPQQKvkJ2pdOjG4TZyEVuFDnlOF6KP08lDaaceVyw/W63mDuafQT+MKHCvXIPpE5uYWeM0rT4w==} - '@fastify/cookie@10.0.0': - resolution: {integrity: sha512-S43spazwAfzm5nKlqq/spAGW+O6r+WQzg5vXXI1ArCXXFa8KBA/tiU3XRVQUehSNtbN5PA6+g183hzh5/dZ6Iw==} + '@fastify/cookie@11.0.1': + resolution: {integrity: sha512-n1Ooz4bgQ5LcOlJQboWPfsMNxIrGV0SgU85UkctdpTlCQE0mtA3rlspOPUdqk9ubiiZn053ucnia4DjTquI4/g==} - '@fastify/cors@10.0.0': - resolution: {integrity: sha512-kb9fkc/LVbLTQ3lhA+ZZjC/Styzysodo/MTCdVCvTtgHa/gBwxrEEkcp3fuoKIfAQt85wksrpXjUGbw5NQffEQ==} + '@fastify/cors@10.0.1': + resolution: {integrity: sha512-O8JIf6448uQbOgzSkCqhClw6gFTAqrdfeA6R3fc/3gwTJGUp7gl8/3tbNB+6INuu4RmgVOq99BmvdGbtu5pgOA==} '@fastify/deepmerge@2.0.0': resolution: {integrity: sha512-fsaybTGDyQ5KpPsplQqb9yKdCf2x/pbNpMNk8Tvp3rRz7lVcupKysH4b2ELMN2P4Hak1+UqTYdTj/u4FNV2p0g==} @@ -2791,20 +2570,20 @@ packages: '@fastify/error@4.0.0': resolution: {integrity: sha512-OO/SA8As24JtT1usTUTKgGH7uLvhfwZPwlptRi2Dp5P4KKmJI3gvsZ8MIHnNwDs4sLf/aai5LzTyl66xr7qMxA==} - '@fastify/express@4.0.0': - resolution: {integrity: sha512-e+IMKKV9+HRCVm7LVW8PaMrpEerHfqNLpRkbiVHYfVm0xeOphiwyNEoge4VA3Sh8gubtDfo9yKkpRzx6gx63kg==} + '@fastify/express@4.0.1': + resolution: {integrity: sha512-mEQ6pawaENeZ3swqVtkxdLi8NQC5eKBkclE+7ma1qQMuB+yI6WxDyEp55pdbqPIqBQTN/cGgHv84qxVS7NKC2Q==} '@fastify/fast-json-stringify-compiler@5.0.1': resolution: {integrity: sha512-f2d3JExJgFE3UbdFcpPwqNUEoHWmt8pAKf8f+9YuLESdefA0WgqxeT6DrGL4Yrf/9ihXNSKOqpjEmurV405meA==} - '@fastify/http-proxy@10.0.0': - resolution: {integrity: sha512-n5/EPspNKtzpCUavuDflYtvtB+aEkablb2sZM83gDKbxM9GF+93maJYQrGozJ2HNRqpt7wfzsDeUuGVFFkYzMQ==} + '@fastify/http-proxy@10.0.1': + resolution: {integrity: sha512-wCMwI9RXK5ISe9G1FGPDCCD2KlSAuLtDDU8XBEfiBxYV0nt+aYm4vPhU/0+IhUM6t+r7UWiV+9OYaJxcTem9+g==} '@fastify/merge-json-schemas@0.1.1': resolution: {integrity: sha512-fERDVz7topgNjtXsJTTW1JKLy0rhuLRcquYqNR9rF7OcVpCa2OVW49ZPDIhaRRCaUuvVxI+N416xUoF76HNSXA==} - '@fastify/multipart@9.0.0': - resolution: {integrity: sha512-B/rzOl1wmkj4LddH2i+zR8Gke8ZX1J8D7n4uJeis5VdIa7OR9Ys/TzUxI0/h1SF9ubHlNhBP+eO/FwnftarP9w==} + '@fastify/multipart@9.0.1': + resolution: {integrity: sha512-vt2gOCw/O4EwpN4KlLVJxth4iQlDf7T5ggw2Db2C+UbO2WJBG7y0jEBvu/HT6JIW/lBYaqrrUy9MmTpCKgXEpw==} '@fastify/reply-from@11.0.1': resolution: {integrity: sha512-F2Qk88gcqIIiug9V+4I6WeeV1faj1Wu798JyOnwbJcjQhm4LYrHdkpFSVwJE0g1cVjYCFFmH3OVh1HHaninttQ==} @@ -2812,14 +2591,8 @@ packages: '@fastify/send@3.1.1': resolution: {integrity: sha512-LdiV2mle/2tH8vh6GwGl0ubfUAgvY+9yF9oGI1iiwVyNUVOQamvw5n+OFu6iCNNoyuCY80FFURBn4TZCbTe8LA==} - '@fastify/static@8.0.0': - resolution: {integrity: sha512-VKGn1PQslB2VqzspyMKPu9xasF9vj+YuyGhVLb1ih6V60VVcRvcf0fFRcl3opt6c6YWwhKKdTUTfVE6COnpw6A==} - - '@fastify/static@8.0.1': - resolution: {integrity: sha512-7idyhbcgf14v4bjWzUeHEFvnVxvNJ1n5cyGPgFtwTZjnjUQ1wgC7a2FQai7OGKqCKywDEjzbPhAZRW+uEK1LMg==} - - '@fastify/view@10.0.0': - resolution: {integrity: sha512-2KnfgpSbAImKV5kKdNAkSyjV+9kYUYLvgDLx/wlzgqel92bN9Z520cwG3g3bAkr0yVnEJu62dIm2qAL9FASS1w==} + '@fastify/static@8.0.2': + resolution: {integrity: sha512-xJ+XaZVl4Y+lKztx8jGi+BE73aByhOmjMgaTx98E4XtVZxUpiaYQIMBlwACsJz+xohm0kvzV34BZoiZ+bsJtBQ==} '@fastify/view@10.0.1': resolution: {integrity: sha512-rXtBN0oVDmoRZAS7lelrCIahf+qFtlMOOas8VPdA7JvrJ9ChcF7e36pIUPU0Vbs3KmHxESUb7XatavUZEe/k5Q==} @@ -2849,9 +2622,18 @@ packages: '@hexagon/base64@1.1.27': resolution: {integrity: sha512-PdUmzpvcUM3Rh39kvz9RdbPVYhMjBjdV7Suw7ZduP7urRLsZR8l5tzgSWKm7TExwBYDFwTnYrZbnE0rQ3N5NLQ==} + '@humanfs/core@0.19.1': + resolution: {integrity: sha512-5DyQ4+1JEUzejeK1JGICcideyfUbGixgS9jNgex5nqkW+cY7WZhxBigmieN5Qnw9ZosSNVC9KQKyb+GUaGyKUA==} + engines: {node: '>=18.18.0'} + + '@humanfs/node@0.16.6': + resolution: {integrity: sha512-YuI2ZHQL78Q5HbhDiBA1X4LmYdXCKCMQIfw0pw7piHJwyREFebJUvrQN4cMssyES6x+vfUbx1CIpaQUKYdQZOw==} + engines: {node: '>=18.18.0'} + '@humanwhocodes/config-array@0.11.14': resolution: {integrity: sha512-3T8LkOmg45BV5FICb15QQMsyUSWrQ8AygVfC7ZG32zOalnqrilm018ZVCw0eapXux8FtA33q8PSRSstjee3jSg==} engines: {node: '>=10.10.0'} + deprecated: Use @eslint/config-array instead '@humanwhocodes/module-importer@1.0.1': resolution: {integrity: sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==} @@ -2863,11 +2645,16 @@ packages: '@humanwhocodes/object-schema@2.0.3': resolution: {integrity: sha512-93zYdMES/c1D69yZiKDBj0V24vqNzB/koF26KPaagAfd3P/4gUlh3Dys5ogAK+Exi9QyzlD8x/08Zt7wIKcDcA==} + deprecated: Use @eslint/object-schema instead '@humanwhocodes/retry@0.3.0': resolution: {integrity: sha512-d2CGZR2o7fS6sWB7DG/3a95bGKQyHMACZ5aW8qGkkqQpUoZV6C0X7Pc7l4ZNMZkfNBf4VWNe9E1jRsf0G146Ew==} engines: {node: '>=18.18'} + '@humanwhocodes/retry@0.4.1': + resolution: {integrity: sha512-c7hNEllBlenFTHBky65mhq8WD2kbN9Q6gk0bTk8lSBvc554jpXSkST1iePudpt7+A/AQvuHs9EMqjHDXMY1lrA==} + engines: {node: '>=18.18'} + '@img/sharp-darwin-arm64@0.33.5': resolution: {integrity: sha512-UT4p+iz/2H4twwAoLCqfA9UH5pI6DggwKEGuaPy7nCVQ8ZsiY5PIcrRvD1DzuY3qYL07NtIQcWnBSY/heikIFQ==} engines: {node: ^18.17.0 || ^20.3.0 || >=21.0.0} @@ -2973,21 +2760,25 @@ packages: cpu: [x64] os: [win32] - '@inquirer/confirm@3.1.6': - resolution: {integrity: sha512-Mj4TU29g6Uy+37UtpA8UpEOI2icBfpCwSW1QDtfx60wRhUy90s/kHPif2OXSSvuwDQT1lhAYRWUfkNf9Tecxvg==} + '@inquirer/confirm@5.0.2': + resolution: {integrity: sha512-KJLUHOaKnNCYzwVbryj3TNBxyZIrr56fR5N45v6K9IPrbT6B7DcudBMfylkV1A8PUdJE15mybkEQyp2/ZUpxUA==} engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' - '@inquirer/core@8.1.0': - resolution: {integrity: sha512-kfx0SU9nWgGe1f03ao/uXc85SFH1v2w3vQVH7QDGjKxdtJz+7vPitFtG++BTyJMYyYgH8MpXigutcXJeiQwVRw==} + '@inquirer/core@10.1.0': + resolution: {integrity: sha512-I+ETk2AL+yAVbvuKx5AJpQmoaWhpiTFOg/UJb7ZkMAK4blmtG8ATh5ct+T/8xNld0CZG/2UhtkdMwpgvld92XQ==} engines: {node: '>=18'} - '@inquirer/figures@1.0.1': - resolution: {integrity: sha512-mtup3wVKia3ZwULPHcbs4Mor8Voi+iIXEWD7wCNbIO6lYR62oPCTQyrddi5OMYVXHzeCSoneZwJuS8sBvlEwDw==} + '@inquirer/figures@1.0.8': + resolution: {integrity: sha512-tKd+jsmhq21AP1LhexC0pPwsCxEhGgAkg28byjJAd+xhmIs8LUX8JbUc3vBf3PhLxWiB5EvyBE5X7JSPAqMAqg==} engines: {node: '>=18'} - '@inquirer/type@1.3.1': - resolution: {integrity: sha512-Pe3PFccjPVJV1vtlfVvm9OnlbxqdnP5QcscFEFEnK5quChf1ufZtM0r8mR5ToWHMxZOh0s8o/qp9ANGRTo/DAw==} + '@inquirer/type@3.0.1': + resolution: {integrity: sha512-+ksJMIy92sOAiAccGpcKZUc3bYO07cADnscIxHBknEm3uNts3movSmBofc1908BNy5edKscxYeAdaX1NXkHS6A==} engines: {node: '>=18'} + peerDependencies: + '@types/node': '>=18' '@ioredis/commands@1.2.0': resolution: {integrity: sha512-Sx1pU8EM64o2BrqNpEO1CNLtKQwyhuXuqyfH7oGKCk+1a33d2r5saW8zNwm3j6BTExtjrv2BxTgzzkMwts6vGg==} @@ -3098,12 +2889,18 @@ packages: '@jridgewell/source-map@0.3.6': resolution: {integrity: sha512-1ZJTZebgqllO79ue2bm3rIGud/bOe0pP5BjSRCRxxYkEZS8STV7zN84UBbiYu7jy+eCKSnVIUgoWWE/tt+shMQ==} + '@jridgewell/sourcemap-codec@1.4.14': + resolution: {integrity: sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw==} + '@jridgewell/sourcemap-codec@1.4.15': resolution: {integrity: sha512-eF2rxCRulEKXHTRiDrDy6erMYWqNw4LPdQ8UQA4huuxaQsVeRPFl2oM8oDGxMFhJUWZf9McpLtJasDDZb/Bpeg==} '@jridgewell/sourcemap-codec@1.5.0': resolution: {integrity: sha512-gv3ZRaISU3fjPAgNsriBRqGWQL6quFx04YMPW/zD8XMLsU32mhCCbfbO6KZFLjvYpCZ8zyDEgqsgf+PwPaM7GQ==} + '@jridgewell/trace-mapping@0.3.18': + resolution: {integrity: sha512-w+niJYzMHdd7USdiH2U6869nqhD2nbfZXND5Yp93qIbEmnDNk7PD48o+YchRVpzMU7M6jVCbenTR7PA1FLQ9pA==} + '@jridgewell/trace-mapping@0.3.25': resolution: {integrity: sha512-vNk6aEwybGtawWmy/PzwnGDOjCkLWSD2wqvjGGAgOAwCGWySYXfYoxt00IJkTF+8Lb57DwOb3Aa0o9CApepiYQ==} @@ -3139,8 +2936,8 @@ packages: '@microsoft/api-extractor-model@7.29.8': resolution: {integrity: sha512-t3Z/xcO6TRbMcnKGVMs4uMzv/gd5j0NhMiJIGjD4cJMeFJ1Hf8wnLSx37vxlRlL0GWlGJhnFgxvnaL6JlS+73g==} - '@microsoft/api-extractor@7.47.9': - resolution: {integrity: sha512-TTq30M1rikVsO5wZVToQT/dGyJY7UXJmjiRtkHPLb74Prx3Etw8+bX7Bv7iLuby6ysb7fuu1NFWqma+csym8Jw==} + '@microsoft/api-extractor@7.47.11': + resolution: {integrity: sha512-lrudfbPub5wzBhymfFtgZKuBvXxoSIAdrvS2UbHjoMT2TjIEddq6Z13pcve7A03BAouw0x8sW8G4txdgfiSwpQ==} hasBin: true '@microsoft/tsdoc-config@0.17.0': @@ -3202,12 +2999,8 @@ packages: cpu: [x64] os: [win32] - '@mswjs/interceptors@0.29.1': - resolution: {integrity: sha512-3rDakgJZ77+RiQUuSK69t1F0m8BQKA8Vh5DCS5V0DWvNY67zob2JhhQrhCO0AKLGINTRSFd1tBaHcJTkhefoSw==} - engines: {node: '>=18'} - - '@mswjs/interceptors@0.35.9': - resolution: {integrity: sha512-SSnyl/4ni/2ViHKkiZb8eajA/eN1DNFaHjhGiLUdZvDz6PKF4COSf/17xqSz64nOo2Ia29SA6B2KNCsyCbVmaQ==} + '@mswjs/interceptors@0.36.10': + resolution: {integrity: sha512-GXrJgakgJW3DWKueebkvtYgGKkxA7s0u5B0P5syJM5rvQUnrpLPigvci8Hukl7yEM+sU06l+er2Fgvx/gmiRgg==} engines: {node: '>=18'} '@napi-rs/canvas-android-arm64@0.1.56': @@ -3268,8 +3061,8 @@ packages: resolution: {integrity: sha512-SujSchzG6lLc/wT+Mwxam/w30Kk2sFTiU6bLFcidecKSmlhenAhGMQhZh2iGFfKoh2+8iit0jrt99n6TqReICQ==} engines: {node: '>= 10'} - '@nestjs/common@10.4.3': - resolution: {integrity: sha512-4hbLd3XIJubHSylYd/1WSi4VQvG68KM/ECYpMDqA3k3J1/T17SAg40sDoq3ZoO5OZgU0xuNyjuISdOTjs11qVg==} + '@nestjs/common@10.4.7': + resolution: {integrity: sha512-gIOpjD3Mx8gfYGxYm/RHPcJzqdknNNFCyY+AxzBT3gc5Xvvik1Dn5OxaMGw5EbVfhZgJKVP0n83giUOAlZQe7w==} peerDependencies: class-transformer: '*' class-validator: '*' @@ -3281,8 +3074,8 @@ packages: class-validator: optional: true - '@nestjs/core@10.4.3': - resolution: {integrity: sha512-6OQz+5C8mT8yRtfvE5pPCq+p6w5jDot+oQku1KzQ24ABn+lay1KGuJwcKZhdVNuselx+8xhdMxknZTA8wrGLIg==} + '@nestjs/core@10.4.7': + resolution: {integrity: sha512-AIpQzW/vGGqSLkKvll1R7uaSNv99AxZI2EFyVJPNGDgFsfXaohfV1Ukl6f+s75Km+6Fj/7aNl80EqzNWQCS8Ig==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/microservices': ^10.0.0 @@ -3298,14 +3091,14 @@ packages: '@nestjs/websockets': optional: true - '@nestjs/platform-express@10.4.3': - resolution: {integrity: sha512-ss7gkofVm3eO+1P9iRhmGq6Xcjg+mIN3dWisKJZYelSV+msb0QpJmqChLvWjLkWtlqDnx915FKUk0IzCa0TVzw==} + '@nestjs/platform-express@10.4.7': + resolution: {integrity: sha512-q6XDOxZPTZ9cxALcVuKUlRBk+cVEv6dW2S8p2yVre22kpEQxq53/OI8EseDvzObGb6hepZ8+yBY04qoYqSlXNQ==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 - '@nestjs/testing@10.4.3': - resolution: {integrity: sha512-SBNWrMU51YAlYmW86wyjlGZ2uLnASNiOPD0lBcNIlxxei0b05/aI3nh7OPuxbXQUdedUJfPq2d2jZj4TRG4S0w==} + '@nestjs/testing@10.4.7': + resolution: {integrity: sha512-aS3sQ0v4g8cyHDzW3xJv1+8MiFAkxUNXmnau588IFFI/nBIo/kevLNHNPr85keYekkJ/lwNDW72h8UGg8BYd9w==} peerDependencies: '@nestjs/common': ^10.0.0 '@nestjs/core': ^10.0.0 @@ -3317,9 +3110,9 @@ packages: '@nestjs/platform-express': optional: true - '@noble/hashes@1.4.0': - resolution: {integrity: sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg==} - engines: {node: '>= 16'} + '@noble/hashes@1.5.0': + resolution: {integrity: sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA==} + engines: {node: ^14.21.3 || >=16} '@nodelib/fs.scandir@2.1.5': resolution: {integrity: sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==} @@ -3362,6 +3155,14 @@ packages: resolution: {integrity: sha512-qnSqB2DQ9TPP96dl8cDubDvrUyWc0/sK81xHTK8eSUspzDM3bsewX903qclQFvVhgStjRWdC5bLb3kQqMkfV5A==} engines: {node: '>=14'} + '@opentelemetry/api-logs@0.53.0': + resolution: {integrity: sha512-8HArjKx+RaAI8uEIgcORbZIPklyh1YLjPSBus8hjRmvLi6DeFzgOcdZ7KwPabKj8mXF8dX0hyfAyGfycz0DbFw==} + engines: {node: '>=14'} + + '@opentelemetry/api-logs@0.54.2': + resolution: {integrity: sha512-4MTVwwmLgUh5QrJnZpYo6YRO5IBLAggf2h8gWDblwRagDStY13aEvt7gGk3jewrMaPlHiF83fENhIx0HO97/cQ==} + engines: {node: '>=14'} + '@opentelemetry/api@1.9.0': resolution: {integrity: sha512-3giAOQvZiH5F9bMlMiv8+GSPMeqg0dbaeo58/0SlA9sxSqZhnUtxzX9/2FzyhS9sWQf5S0GJE0AKBrFqjpeYcg==} engines: {node: '>=8.0.0'} @@ -3372,156 +3173,214 @@ packages: peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.24.1': - resolution: {integrity: sha512-wMSGfsdmibI88K9wB498zXY04yThPexo8jvwNNlm542HZB7XrrMRBbAyKJqG8qDRJwIBdBrPMi4V9ZPW/sqrcg==} + '@opentelemetry/core@1.25.1': + resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' + + '@opentelemetry/core@1.26.0': + resolution: {integrity: sha512-1iKxXXE8415Cdv0yjG3G6hQnB5eVEsJce3QaawX8SjDn0mAS0ZM8fAbZZJD4ajvhC15cePvosSCut404KrIIvQ==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.9.0' + '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/core@1.25.1': - resolution: {integrity: sha512-GeT/l6rBYWVQ4XArluLVB6WWQ8flHbdb6r2FCHC3smtdOAbrJBIv35tpV/yp9bmYUJf+xmZpu9DRTIeJVhFbEQ==} + '@opentelemetry/core@1.28.0': + resolution: {integrity: sha512-ZLwRMV+fNDpVmF2WYUdBHlq0eOWtEaUJSusrzjGnBt7iSRvfjFE3RXYUZJrqou/wIDWV0DwQ5KIfYe9WXg9Xqw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/instrumentation-connect@0.38.0': - resolution: {integrity: sha512-2/nRnx3pjYEmdPIaBwtgtSviTKHWnDZN3R+TkRUnhIVrvBKVcq+I5B2rtd6mr6Fe9cHlZ9Ojcuh7pkNh/xdWWg==} + '@opentelemetry/instrumentation-amqplib@0.43.0': + resolution: {integrity: sha512-ALjfQC+0dnIEcvNYsbZl/VLh7D2P1HhFF4vicRKHhHFIUV3Shpg4kXgiek5PLhmeKSIPiUB25IYH5RIneclL4A==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-express@0.41.0': - resolution: {integrity: sha512-/B7fbMdaf3SYe5f1P973tkqd6s7XZirjpfkoJ63E7nltU30qmlgm9tY5XwZOzAFI0rHS9tbrFI2HFPAvQUFe/A==} + '@opentelemetry/instrumentation-connect@0.40.0': + resolution: {integrity: sha512-3aR/3YBQ160siitwwRLjwqrv2KBT16897+bo6yz8wIfel6nWOxTZBJudcbsK3p42pTC7qrbotJ9t/1wRLpv79Q==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-fastify@0.38.0': - resolution: {integrity: sha512-HBVLpTSYpkQZ87/Df3N0gAw7VzYZV3n28THIBrJWfuqw3Or7UqdhnjeuMIPQ04BKk3aZc0cWn2naSQObbh5vXw==} + '@opentelemetry/instrumentation-dataloader@0.12.0': + resolution: {integrity: sha512-pnPxatoFE0OXIZDQhL2okF//dmbiWFzcSc8pUg9TqofCLYZySSxDCgQc69CJBo5JnI3Gz1KP+mOjS4WAeRIH4g==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-graphql@0.42.0': - resolution: {integrity: sha512-N8SOwoKL9KQSX7z3gOaw5UaTeVQcfDO1c21csVHnmnmGUoqsXbArK2B8VuwPWcv6/BC/i3io+xTo7QGRZ/z28Q==} + '@opentelemetry/instrumentation-express@0.44.0': + resolution: {integrity: sha512-GWgibp6Q0wxyFaaU8ERIgMMYgzcHmGrw3ILUtGchLtLncHNOKk0SNoWGqiylXWWT4HTn5XdV8MGawUgpZh80cA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-hapi@0.40.0': - resolution: {integrity: sha512-8U/w7Ifumtd2bSN1OLaSwAAFhb9FyqWUki3lMMB0ds+1+HdSxYBe9aspEJEgvxAqOkrQnVniAPTEGf1pGM7SOw==} + '@opentelemetry/instrumentation-fastify@0.41.0': + resolution: {integrity: sha512-pNRjFvf0mvqfJueaeL/qEkuGJwgtE5pgjIHGYwjc2rMViNCrtY9/Sf+Nu8ww6dDd/Oyk2fwZZP7i0XZfCnETrA==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-http@0.52.1': - resolution: {integrity: sha512-dG/aevWhaP+7OLv4BQQSEKMJv8GyeOp3Wxl31NHqE8xo9/fYMfEljiZphUHIfyg4gnZ9swMyWjfOQs5GUQe54Q==} + '@opentelemetry/instrumentation-fs@0.16.0': + resolution: {integrity: sha512-hMDRUxV38ln1R3lNz6osj3YjlO32ykbHqVrzG7gEhGXFQfu7LJUx8t9tEwE4r2h3CD4D0Rw4YGDU4yF4mP3ilg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-ioredis@0.42.0': - resolution: {integrity: sha512-P11H168EKvBB9TUSasNDOGJCSkpT44XgoM6d3gRIWAa9ghLpYhl0uRkS8//MqPzcJVHr3h3RmfXIpiYLjyIZTw==} + '@opentelemetry/instrumentation-generic-pool@0.39.0': + resolution: {integrity: sha512-y4v8Y+tSfRB3NNBvHjbjrn7rX/7sdARG7FuK6zR8PGb28CTa0kHpEGCJqvL9L8xkTNvTXo+lM36ajFGUaK1aNw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-koa@0.42.0': - resolution: {integrity: sha512-H1BEmnMhho8o8HuNRq5zEI4+SIHDIglNB7BPKohZyWG4fWNuR7yM4GTlR01Syq21vODAS7z5omblScJD/eZdKw==} + '@opentelemetry/instrumentation-graphql@0.44.0': + resolution: {integrity: sha512-FYXTe3Bv96aNpYktqm86BFUTpjglKD0kWI5T5bxYkLUPEPvFn38vWGMJTGrDMVou/i55E4jlWvcm6hFIqLsMbg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongodb@0.46.0': - resolution: {integrity: sha512-VF/MicZ5UOBiXrqBslzwxhN7TVqzu1/LN/QDpkskqM0Zm0aZ4CVRbUygL8d7lrjLn15x5kGIe8VsSphMfPJzlA==} + '@opentelemetry/instrumentation-hapi@0.41.0': + resolution: {integrity: sha512-jKDrxPNXDByPlYcMdZjNPYCvw0SQJjN+B1A+QH+sx+sAHsKSAf9hwFiJSrI6C4XdOls43V/f/fkp9ITkHhKFbQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mongoose@0.40.0': - resolution: {integrity: sha512-niRi5ZUnkgzRhIGMOozTyoZIvJKNJyhijQI4nF4iFSb+FUx2v5fngfR+8XLmdQAO7xmsD8E5vEGdDVYVtKbZew==} + '@opentelemetry/instrumentation-http@0.53.0': + resolution: {integrity: sha512-H74ErMeDuZfj7KgYCTOFGWF5W9AfaPnqLQQxeFq85+D29wwV2yqHbz2IKLYpkOh7EI6QwDEl7rZCIxjJLyc/CQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql2@0.40.0': - resolution: {integrity: sha512-0xfS1xcqUmY7WE1uWjlmI67Xg3QsSUlNT+AcXHeA4BDUPwZtWqF4ezIwLgpVZfHOnkAEheqGfNSWd1PIu3Wnfg==} + '@opentelemetry/instrumentation-ioredis@0.43.0': + resolution: {integrity: sha512-i3Dke/LdhZbiUAEImmRG3i7Dimm/BD7t8pDDzwepSvIQ6s2X6FPia7561gw+64w+nx0+G9X14D7rEfaMEmmjig==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-mysql@0.40.0': - resolution: {integrity: sha512-d7ja8yizsOCNMYIJt5PH/fKZXjb/mS48zLROO4BzZTtDfhNCl2UM/9VIomP2qkGIFVouSJrGr/T00EzY7bPtKA==} + '@opentelemetry/instrumentation-kafkajs@0.4.0': + resolution: {integrity: sha512-I9VwDG314g7SDL4t8kD/7+1ytaDBRbZQjhVaQaVIDR8K+mlsoBhLsWH79yHxhHQKvwCSZwqXF+TiTOhoQVUt7A==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-nestjs-core@0.39.0': - resolution: {integrity: sha512-mewVhEXdikyvIZoMIUry8eb8l3HUjuQjSjVbmLVTt4NQi35tkpnHQrG9bTRBrl3403LoWZ2njMPJyg4l6HfKvA==} + '@opentelemetry/instrumentation-knex@0.41.0': + resolution: {integrity: sha512-OhI1SlLv5qnsnm2dOVrian/x3431P75GngSpnR7c4fcVFv7prXGYu29Z6ILRWJf/NJt6fkbySmwdfUUnFnHCTg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-pg@0.43.0': - resolution: {integrity: sha512-og23KLyoxdnAeFs1UWqzSonuCkePUzCX30keSYigIzJe/6WSYA8rnEI5lobcxPEzg+GcU06J7jzokuEHbjVJNw==} + '@opentelemetry/instrumentation-koa@0.43.0': + resolution: {integrity: sha512-lDAhSnmoTIN6ELKmLJBplXzT/Jqs5jGZehuG22EdSMaTwgjMpxMDI1YtlKEhiWPWkrz5LUsd0aOO0ZRc9vn3AQ==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation-redis-4@0.41.0': - resolution: {integrity: sha512-H7IfGTqW2reLXqput4yzAe8YpDC0fmVNal95GHMLOrS89W+qWUKIqxolSh63hJyfmwPSFwXASzj7wpSk8Az+Dg==} + '@opentelemetry/instrumentation-lru-memoizer@0.40.0': + resolution: {integrity: sha512-21xRwZsEdMPnROu/QsaOIODmzw59IYpGFmuC4aFWvMj6stA8+Ei1tX67nkarJttlNjoM94um0N4X26AD7ff54A==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.46.0': - resolution: {integrity: sha512-a9TijXZZbk0vI5TGLZl+0kxyFfrXHhX6Svtz7Pp2/VBlCSKrazuULEyoJQrOknJyFWNMEmbbJgOciHCCpQcisw==} + '@opentelemetry/instrumentation-mongodb@0.48.0': + resolution: {integrity: sha512-9YWvaGvrrcrydMsYGLu0w+RgmosLMKe3kv/UNlsPy8RLnCkN2z+bhhbjjjuxtUmvEuKZMCoXFluABVuBr1yhjw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/instrumentation@0.52.1': - resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + '@opentelemetry/instrumentation-mongoose@0.42.0': + resolution: {integrity: sha512-AnWv+RaR86uG3qNEMwt3plKX1ueRM7AspfszJYVkvkehiicC3bHQA6vWdb6Zvy5HAE14RyFbu9+2hUUjR2NSyg==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': ^1.3.0 - '@opentelemetry/redis-common@0.36.2': - resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} + '@opentelemetry/instrumentation-mysql2@0.41.0': + resolution: {integrity: sha512-REQB0x+IzVTpoNgVmy5b+UnH1/mDByrneimP6sbDHkp1j8QOl1HyWOrBH/6YWR0nrbU3l825Em5PlybjT3232g==} engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/resources@1.24.1': - resolution: {integrity: sha512-cyv0MwAaPF7O86x5hk3NNgenMObeejZFLJJDVuSeSMIsknlsj3oOZzRv3qSzlwYomXsICfBeFFlxwHQte5mGXQ==} + '@opentelemetry/instrumentation-mysql@0.41.0': + resolution: {integrity: sha512-jnvrV6BsQWyHS2qb2fkfbfSb1R/lmYwqEZITwufuRl37apTopswu9izc0b1CYRp/34tUG/4k/V39PND6eyiNvw==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.9.0' + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/resources@1.25.1': - resolution: {integrity: sha512-pkZT+iFYIZsVn6+GzM0kSX+u3MSLCY9md+lIJOoKl/P+gJFfxJte/60Usdp8Ce4rOs8GduUpSPNe1ddGyDT1sQ==} + '@opentelemetry/instrumentation-nestjs-core@0.40.0': + resolution: {integrity: sha512-WF1hCUed07vKmf5BzEkL0wSPinqJgH7kGzOjjMAiTGacofNXjb/y4KQ8loj2sNsh5C/NN7s1zxQuCgbWbVTGKg==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.0.0 <1.10.0' + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-pg@0.44.0': + resolution: {integrity: sha512-oTWVyzKqXud1BYEGX1loo2o4k4vaU1elr3vPO8NZolrBtFvQ34nx4HgUaexUDuEog00qQt+MLR5gws/p+JXMLQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-redis-4@0.42.0': + resolution: {integrity: sha512-NaD+t2JNcOzX/Qa7kMy68JbmoVIV37fT/fJYzLKu2Wwd+0NCxt+K2OOsOakA8GVg8lSpFdbx4V/suzZZ2Pvdjg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-tedious@0.15.0': + resolution: {integrity: sha512-Kb7yo8Zsq2TUwBbmwYgTAMPK0VbhoS8ikJ6Bup9KrDtCx2JC01nCb+M0VJWXt7tl0+5jARUbKWh5jRSoImxdCw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation-undici@0.6.0': + resolution: {integrity: sha512-ABJBhm5OdhGmbh0S/fOTE4N69IZ00CsHC5ijMYfzbw3E5NwLgpQk5xsljaECrJ8wz1SfXbO03FiSuu5AyRAkvQ==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.7.0 - '@opentelemetry/sdk-metrics@1.24.1': - resolution: {integrity: sha512-FrAqCbbGao9iKI+Mgh+OsC9+U2YMoXnlDHe06yH7dvavCKzE3S892dGtX54+WhSFVxHR/TMRVJiK/CV93GR0TQ==} + '@opentelemetry/instrumentation@0.52.1': + resolution: {integrity: sha512-uXJbYU/5/MBHjMp1FqrILLRuiJCs3Ofk0MeRDk8g1S1gD47U8X3JnSwcMO1rtRo1x1a7zKaQHaoYu49p/4eSKw==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/instrumentation@0.53.0': + resolution: {integrity: sha512-DMwg0hy4wzf7K73JJtl95m/e0boSoWhH07rfvHvYzQtBD3Bmv0Wc1x733vyZBqmFm8OjJD0/pfiUg1W3JjFX0A==} engines: {node: '>=14'} peerDependencies: - '@opentelemetry/api': '>=1.3.0 <1.9.0' + '@opentelemetry/api': ^1.3.0 - '@opentelemetry/sdk-trace-base@1.25.1': - resolution: {integrity: sha512-C8k4hnEbc5FamuZQ92nTOp8X/diCY56XUTnMiv9UTuJitCzaNNHAVsdm5+HLCdI8SLQsLWIrG38tddMxLVoftw==} + '@opentelemetry/instrumentation@0.54.2': + resolution: {integrity: sha512-go6zpOVoZVztT9r1aPd79Fr3OWiD4N24bCPJsIKkBses8oyFo12F/Ew3UBTdIu6hsW4HC4MVEJygG6TEyJI/lg==} + engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': ^1.3.0 + + '@opentelemetry/redis-common@0.36.2': + resolution: {integrity: sha512-faYX1N0gpLhej/6nyp6bgRjzAKXn5GOEMYY7YhciSfCoITAktLUtQ36d24QEWNA1/WA1y6qQunCe0OhHRkVl9g==} + engines: {node: '>=14'} + + '@opentelemetry/resources@1.28.0': + resolution: {integrity: sha512-cIyXSVJjGeTICENN40YSvLDAq4Y2502hGK3iN7tfdynQLKWb3XWZQEkPc+eSx47kiy11YeFAlYkEfXwR1w8kfw==} engines: {node: '>=14'} peerDependencies: '@opentelemetry/api': '>=1.0.0 <1.10.0' - '@opentelemetry/semantic-conventions@1.24.1': - resolution: {integrity: sha512-VkliWlS4/+GHLLW7J/rVBA00uXus1SWvwFvcUDxDwmFxYfg/2VI6ekwdXS28cjI8Qz2ky2BzG8OUHo+WeYIWqw==} + '@opentelemetry/sdk-trace-base@1.28.0': + resolution: {integrity: sha512-ceUVWuCpIao7Y5xE02Xs3nQi0tOGmMea17ecBdwtCvdo9ekmO+ijc9RFDgfifMl7XCBf41zne/1POM3LqSTZDA==} engines: {node: '>=14'} + peerDependencies: + '@opentelemetry/api': '>=1.0.0 <1.10.0' '@opentelemetry/semantic-conventions@1.25.1': resolution: {integrity: sha512-ZDjMJJQRlyk8A1KZFCc+bCbsyrn1wTwdNt56F7twdfUfnHUZUq77/WfONCj8p72NZOyP7pNTdUWSTYC3GTbuuQ==} engines: {node: '>=14'} + '@opentelemetry/semantic-conventions@1.27.0': + resolution: {integrity: sha512-sAay1RrB+ONOem0OZanAR1ZI/k7yDpnOQSQmTMuGImUQb2y8EbSaCJ94FQluM74xoU03vlb2d2U90hZluL6nQg==} + engines: {node: '>=14'} + + '@opentelemetry/semantic-conventions@1.28.0': + resolution: {integrity: sha512-lp4qAiMTD4sNWW4DbKLBkfiMZ4jbAboJIGOQr5DvciMRI494OapieI9qiODpOt0XBr1LjIDy1xAGAnVs5supTA==} + engines: {node: '>=14'} + '@opentelemetry/sql-common@0.40.1': resolution: {integrity: sha512-nSDlnHSqzC3pXn/wZEZVLuAuJ1MYMXPBwtv2qAbCa3847SaHItdE7SzUq/Jtb0KZmh1zfAbNi3AAMjztTT4Ugg==} engines: {node: '>=14'} @@ -3558,8 +3417,8 @@ packages: resolution: {integrity: sha512-+1VkjdD0QBLPodGrJUeqarH8VAIvQODIbwh9XpP5Syisf7YoQgsJKPNFoqqLQlu+VQ/tVSshMR6loPMn8U+dPg==} engines: {node: '>=14'} - '@prisma/instrumentation@5.17.0': - resolution: {integrity: sha512-c1Sle4ji8aasMcYfBBHFM56We4ljfenVtRmS8aY06BllS7SoU6SmJBwG7vil+GHiR0Yrh+t9iBwt4AY0Jr4KNQ==} + '@prisma/instrumentation@5.19.1': + resolution: {integrity: sha512-VLnzMQq7CWroL5AeaW0Py2huiNKeoMfCH3SUxstdzPrlWQi6UQ9UrfcbUkNHlVFqOMacqy8X/8YtE0kuKDpD9w==} '@readme/better-ajv-errors@1.6.0': resolution: {integrity: sha512-9gO9rld84Jgu13kcbKRU+WHseNhaVt76wYMeRDGsUGYxwJtI3RmEJ9LY9dZCYQGI8eUZLuxb5qDja0nqklpFjQ==} @@ -3598,8 +3457,8 @@ packages: rollup: optional: true - '@rollup/pluginutils@5.1.2': - resolution: {integrity: sha512-/FIdS3PyZ39bjZlwqFnWqCOVnW7o963LtKMwQOD0NhQqw22gSr2YY1afu3FxRip4ZCZNsD5jq6Aaz6QV3D/Njw==} + '@rollup/pluginutils@5.1.3': + resolution: {integrity: sha512-Pnsb6f32CD2W3uCaLZIzDmeFyQ2b8UWMFI7xtwUezpcGBDVDW6y9XgAWIlARiGAo6eNF5FK5aQTr0LFyNyqq5A==} engines: {node: '>=14.0.0'} peerDependencies: rollup: ^1.20.0||^2.0.0||^3.0.0||^4.0.0 @@ -3607,83 +3466,93 @@ packages: rollup: optional: true - '@rollup/rollup-android-arm-eabi@4.22.5': - resolution: {integrity: sha512-SU5cvamg0Eyu/F+kLeMXS7GoahL+OoizlclVFX3l5Ql6yNlywJJ0OuqTzUx0v+aHhPHEB/56CT06GQrRrGNYww==} + '@rollup/rollup-android-arm-eabi@4.26.0': + resolution: {integrity: sha512-gJNwtPDGEaOEgejbaseY6xMFu+CPltsc8/T+diUTTbOQLqD+bnrJq9ulH6WD69TqwqWmrfRAtUv30cCFZlbGTQ==} cpu: [arm] os: [android] - '@rollup/rollup-android-arm64@4.22.5': - resolution: {integrity: sha512-S4pit5BP6E5R5C8S6tgU/drvgjtYW76FBuG6+ibG3tMvlD1h9LHVF9KmlmaUBQ8Obou7hEyS+0w+IR/VtxwNMQ==} + '@rollup/rollup-android-arm64@4.26.0': + resolution: {integrity: sha512-YJa5Gy8mEZgz5JquFruhJODMq3lTHWLm1fOy+HIANquLzfIOzE9RA5ie3JjCdVb9r46qfAQY/l947V0zfGJ0OQ==} cpu: [arm64] os: [android] - '@rollup/rollup-darwin-arm64@4.22.5': - resolution: {integrity: sha512-250ZGg4ipTL0TGvLlfACkIxS9+KLtIbn7BCZjsZj88zSg2Lvu3Xdw6dhAhfe/FjjXPVNCtcSp+WZjVsD3a/Zlw==} + '@rollup/rollup-darwin-arm64@4.26.0': + resolution: {integrity: sha512-ErTASs8YKbqTBoPLp/kA1B1Um5YSom8QAc4rKhg7b9tyyVqDBlQxy7Bf2wW7yIlPGPg2UODDQcbkTlruPzDosw==} cpu: [arm64] os: [darwin] - '@rollup/rollup-darwin-x64@4.22.5': - resolution: {integrity: sha512-D8brJEFg5D+QxFcW6jYANu+Rr9SlKtTenmsX5hOSzNYVrK5oLAEMTUgKWYJP+wdKyCdeSwnapLsn+OVRFycuQg==} + '@rollup/rollup-darwin-x64@4.26.0': + resolution: {integrity: sha512-wbgkYDHcdWW+NqP2mnf2NOuEbOLzDblalrOWcPyY6+BRbVhliavon15UploG7PpBRQ2bZJnbmh8o3yLoBvDIHA==} cpu: [x64] os: [darwin] - '@rollup/rollup-linux-arm-gnueabihf@4.22.5': - resolution: {integrity: sha512-PNqXYmdNFyWNg0ma5LdY8wP+eQfdvyaBAojAXgO7/gs0Q/6TQJVXAXe8gwW9URjbS0YAammur0fynYGiWsKlXw==} + '@rollup/rollup-freebsd-arm64@4.26.0': + resolution: {integrity: sha512-Y9vpjfp9CDkAG4q/uwuhZk96LP11fBz/bYdyg9oaHYhtGZp7NrbkQrj/66DYMMP2Yo/QPAsVHkV891KyO52fhg==} + cpu: [arm64] + os: [freebsd] + + '@rollup/rollup-freebsd-x64@4.26.0': + resolution: {integrity: sha512-A/jvfCZ55EYPsqeaAt/yDAG4q5tt1ZboWMHEvKAH9Zl92DWvMIbnZe/f/eOXze65aJaaKbL+YeM0Hz4kLQvdwg==} + cpu: [x64] + os: [freebsd] + + '@rollup/rollup-linux-arm-gnueabihf@4.26.0': + resolution: {integrity: sha512-paHF1bMXKDuizaMODm2bBTjRiHxESWiIyIdMugKeLnjuS1TCS54MF5+Y5Dx8Ui/1RBPVRE09i5OUlaLnv8OGnA==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm-musleabihf@4.22.5': - resolution: {integrity: sha512-kSSCZOKz3HqlrEuwKd9TYv7vxPYD77vHSUvM2y0YaTGnFc8AdI5TTQRrM1yIp3tXCKrSL9A7JLoILjtad5t8pQ==} + '@rollup/rollup-linux-arm-musleabihf@4.26.0': + resolution: {integrity: sha512-cwxiHZU1GAs+TMxvgPfUDtVZjdBdTsQwVnNlzRXC5QzIJ6nhfB4I1ahKoe9yPmoaA/Vhf7m9dB1chGPpDRdGXg==} cpu: [arm] os: [linux] - '@rollup/rollup-linux-arm64-gnu@4.22.5': - resolution: {integrity: sha512-oTXQeJHRbOnwRnRffb6bmqmUugz0glXaPyspp4gbQOPVApdpRrY/j7KP3lr7M8kTfQTyrBUzFjj5EuHAhqH4/w==} + '@rollup/rollup-linux-arm64-gnu@4.26.0': + resolution: {integrity: sha512-4daeEUQutGRCW/9zEo8JtdAgtJ1q2g5oHaoQaZbMSKaIWKDQwQ3Yx0/3jJNmpzrsScIPtx/V+1AfibLisb3AMQ==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-arm64-musl@4.22.5': - resolution: {integrity: sha512-qnOTIIs6tIGFKCHdhYitgC2XQ2X25InIbZFor5wh+mALH84qnFHvc+vmWUpyX97B0hNvwNUL4B+MB8vJvH65Fw==} + '@rollup/rollup-linux-arm64-musl@4.26.0': + resolution: {integrity: sha512-eGkX7zzkNxvvS05ROzJ/cO/AKqNvR/7t1jA3VZDi2vRniLKwAWxUr85fH3NsvtxU5vnUUKFHKh8flIBdlo2b3Q==} cpu: [arm64] os: [linux] - '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': - resolution: {integrity: sha512-TMYu+DUdNlgBXING13rHSfUc3Ky5nLPbWs4bFnT+R6Vu3OvXkTkixvvBKk8uO4MT5Ab6lC3U7x8S8El2q5o56w==} + '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': + resolution: {integrity: sha512-Odp/lgHbW/mAqw/pU21goo5ruWsytP7/HCC/liOt0zcGG0llYWKrd10k9Fj0pdj3prQ63N5yQLCLiE7HTX+MYw==} cpu: [ppc64] os: [linux] - '@rollup/rollup-linux-riscv64-gnu@4.22.5': - resolution: {integrity: sha512-PTQq1Kz22ZRvuhr3uURH+U/Q/a0pbxJoICGSprNLAoBEkyD3Sh9qP5I0Asn0y0wejXQBbsVMRZRxlbGFD9OK4A==} + '@rollup/rollup-linux-riscv64-gnu@4.26.0': + resolution: {integrity: sha512-MBR2ZhCTzUgVD0OJdTzNeF4+zsVogIR1U/FsyuFerwcqjZGvg2nYe24SAHp8O5sN8ZkRVbHwlYeHqcSQ8tcYew==} cpu: [riscv64] os: [linux] - '@rollup/rollup-linux-s390x-gnu@4.22.5': - resolution: {integrity: sha512-bR5nCojtpuMss6TDEmf/jnBnzlo+6n1UhgwqUvRoe4VIotC7FG1IKkyJbwsT7JDsF2jxR+NTnuOwiGv0hLyDoQ==} + '@rollup/rollup-linux-s390x-gnu@4.26.0': + resolution: {integrity: sha512-YYcg8MkbN17fMbRMZuxwmxWqsmQufh3ZJFxFGoHjrE7bv0X+T6l3glcdzd7IKLiwhT+PZOJCblpnNlz1/C3kGQ==} cpu: [s390x] os: [linux] - '@rollup/rollup-linux-x64-gnu@4.22.5': - resolution: {integrity: sha512-N0jPPhHjGShcB9/XXZQWuWBKZQnC1F36Ce3sDqWpujsGjDz/CQtOL9LgTrJ+rJC8MJeesMWrMWVLKKNR/tMOCA==} + '@rollup/rollup-linux-x64-gnu@4.26.0': + resolution: {integrity: sha512-ZuwpfjCwjPkAOxpjAEjabg6LRSfL7cAJb6gSQGZYjGhadlzKKywDkCUnJ+KEfrNY1jH5EEoSIKLCb572jSiglA==} cpu: [x64] os: [linux] - '@rollup/rollup-linux-x64-musl@4.22.5': - resolution: {integrity: sha512-uBa2e28ohzNNwjr6Uxm4XyaA1M/8aTgfF2T7UIlElLaeXkgpmIJ2EitVNQxjO9xLLLy60YqAgKn/AqSpCUkE9g==} + '@rollup/rollup-linux-x64-musl@4.26.0': + resolution: {integrity: sha512-+HJD2lFS86qkeF8kNu0kALtifMpPCZU80HvwztIKnYwym3KnA1os6nsX4BGSTLtS2QVAGG1P3guRgsYyMA0Yhg==} cpu: [x64] os: [linux] - '@rollup/rollup-win32-arm64-msvc@4.22.5': - resolution: {integrity: sha512-RXT8S1HP8AFN/Kr3tg4fuYrNxZ/pZf1HemC5Tsddc6HzgGnJm0+Lh5rAHJkDuW3StI0ynNXukidROMXYl6ew8w==} + '@rollup/rollup-win32-arm64-msvc@4.26.0': + resolution: {integrity: sha512-WUQzVFWPSw2uJzX4j6YEbMAiLbs0BUysgysh8s817doAYhR5ybqTI1wtKARQKo6cGop3pHnrUJPFCsXdoFaimQ==} cpu: [arm64] os: [win32] - '@rollup/rollup-win32-ia32-msvc@4.22.5': - resolution: {integrity: sha512-ElTYOh50InL8kzyUD6XsnPit7jYCKrphmddKAe1/Ytt74apOxDq5YEcbsiKs0fR3vff3jEneMM+3I7jbqaMyBg==} + '@rollup/rollup-win32-ia32-msvc@4.26.0': + resolution: {integrity: sha512-D4CxkazFKBfN1akAIY6ieyOqzoOoBV1OICxgUblWxff/pSjCA2khXlASUx7mK6W1oP4McqhgcCsu6QaLj3WMWg==} cpu: [ia32] os: [win32] - '@rollup/rollup-win32-x64-msvc@4.22.5': - resolution: {integrity: sha512-+lvL/4mQxSV8MukpkKyyvfwhH266COcWlXE/1qxwN08ajovta3459zrjLghYMgDerlzNwLAcFpvU+WWE5y6nAQ==} + '@rollup/rollup-win32-x64-msvc@4.26.0': + resolution: {integrity: sha512-2x8MO1rm4PGEP0xWbubJW5RtbNLk3puzAMaLQd3B3JHVw4KcHlmXcO+Wewx9zCoo7EUFiMlu/aZbCJ7VjMzAag==} cpu: [x64] os: [win32] @@ -3712,45 +3581,57 @@ packages: '@types/node': optional: true - '@rushstack/ts-command-line@4.22.8': - resolution: {integrity: sha512-XbFjOoV7qZHJnSuFUHv0pKaFA4ixyCuki+xMjsMfDwfvQjs5MYG0IK5COal3tRnG7KCDe2l/G+9LrzYE/RJhgg==} + '@rushstack/ts-command-line@4.23.0': + resolution: {integrity: sha512-jYREBtsxduPV6ptNq8jOKp9+yx0ld1Tb/Tkdnlj8gTjazl1sF3DwX2VbluyYrNd0meWIL0bNeer7WDf5tKFjaQ==} '@sec-ant/readable-stream@0.4.1': resolution: {integrity: sha512-831qok9r2t8AlxLko40y2ebgSDhenenCatLVeW/uBtnHPyhHOvG0C7TvfgecV+wHzIm5KUICgzmVpWS+IMEAeg==} - '@sentry/core@8.20.0': - resolution: {integrity: sha512-R81snuw+67VT4aCxr6ShST/s0Y6FlwN2YczhDwaGyzumn5rlvA6A4JtQDeExduNoDDyv4T3LrmW8wlYZn3CJJw==} + '@sentry/core@8.38.0': + resolution: {integrity: sha512-sGD+5TEHU9G7X7zpyaoJxpOtwjTjvOd1f/MKBrWW2vf9UbYK+GUJrOzLhMoSWp/pHSYgvObkJkDb/HwieQjvhQ==} engines: {node: '>=14.18'} - '@sentry/node@8.20.0': - resolution: {integrity: sha512-i4ywT2m0Gw65U3uwI4NwiNcyqp9YF6/RsusfH1pg4YkiL/RYp7FS0MPVgMggfvoue9S3KjCgRVlzTLwFATyPXQ==} + '@sentry/node@8.38.0': + resolution: {integrity: sha512-nwW0XqZFQseXYn0i6i6nKPkbjgHMBEFSF9TnK6mHHqJHHObHIZ6qu5CfvGKgxATia8JPIg9NN8XcyYOnQMi07w==} engines: {node: '>=14.18'} - '@sentry/opentelemetry@8.20.0': - resolution: {integrity: sha512-NFcLK6+t9wUc4HlGKeuDn6W4KjZxZfZmWlrK2/tgC5KzG1cnVeOnWUrJzGHTa+YDDdIijpjiFUcpXGPkX3rmIg==} + '@sentry/opentelemetry@8.38.0': + resolution: {integrity: sha512-AfjmIf/v7+x2WplhkX66LyGKvrzzPeSgff9uJ0cFCC2s0yd1qA2VPuIwEyr5i/FOJOP5bvFr8tu/hz3LA4+F5Q==} engines: {node: '>=14.18'} peerDependencies: '@opentelemetry/api': ^1.9.0 '@opentelemetry/core': ^1.25.1 - '@opentelemetry/instrumentation': ^0.52.1 - '@opentelemetry/sdk-trace-base': ^1.25.1 - '@opentelemetry/semantic-conventions': ^1.25.1 + '@opentelemetry/instrumentation': ^0.54.0 + '@opentelemetry/sdk-trace-base': ^1.26.0 + '@opentelemetry/semantic-conventions': ^1.27.0 - '@sentry/profiling-node@8.20.0': - resolution: {integrity: sha512-vQaMYjPM7o0qvmj4atxXZywIDhnxMwTlc6x24eKqT8zN0OFBuIc1nYIacT7pEmd7R6e/mXdiG04GH1Vg0bHfOQ==} + '@sentry/profiling-node@8.38.0': + resolution: {integrity: sha512-7I+hANLQRUAciRLzz4nUehEUHYeMNKkMfu6KkBuLcD1F3x7Y/tcxyHlHl+bNKf6tyUdW7IKGb+7Pk/WKMBnZrg==} engines: {node: '>=14.18'} hasBin: true - '@sentry/types@8.20.0': - resolution: {integrity: sha512-6IP278KojOpiAA7vrd1hjhUyn26cl0n0nGsShzic5ztCVs92sTeVRnh7MTB9irDVtAbOEyt/YH6go3h+Jia1pA==} + '@sentry/types@8.38.0': + resolution: {integrity: sha512-fP5H9ZX01W4Z/EYctk3mkSHi7d06cLcX2/UWqwdWbyPWI+pL2QpUPICeO/C+8SnmYx//wFj3qWDhyPCh1PdFAA==} engines: {node: '>=14.18'} - '@sentry/utils@8.20.0': - resolution: {integrity: sha512-+1I5H8dojURiEUGPliDwheQk8dhjp8uV1sMccR/W/zjFrt4wZyPs+Ttp/V7gzm9LDJoNek9tmELert/jQqWTgg==} + '@sentry/utils@8.38.0': + resolution: {integrity: sha512-3X7MgIKIx+2q5Al7QkhaRB4wV6DvzYsaeIwdqKUzGLuRjXmNgJrLoU87TAwQRmZ6Wr3IoEpThZZMNrzYPXxArw==} engines: {node: '>=14.18'} - '@shikijs/core@1.12.0': - resolution: {integrity: sha512-mc1cLbm6UQ8RxLc0dZES7v5rkH+99LxQp/ZvTqV3NLyYsO/fD6JhEflP1H5b2SDq9gI0+0G36AVZWxvounfR9w==} + '@shikijs/core@1.22.2': + resolution: {integrity: sha512-bvIQcd8BEeR1yFvOYv6HDiyta2FFVePbzeowf5pPS1avczrPK+cjmaxxh0nx5QzbON7+Sv0sQfQVciO7bN72sg==} + + '@shikijs/engine-javascript@1.22.2': + resolution: {integrity: sha512-iOvql09ql6m+3d1vtvP8fLCVCK7BQD1pJFmHIECsujB0V32BJ0Ab6hxk1ewVSMFA58FI0pR2Had9BKZdyQrxTw==} + + '@shikijs/engine-oniguruma@1.22.2': + resolution: {integrity: sha512-GIZPAGzQOy56mGvWMoZRPggn0dTlBf1gutV5TdceLCZlFNqWmuc7u+CzD0Gd9vQUTgLbrt0KLzz6FNprqYAxlA==} + + '@shikijs/types@1.22.2': + resolution: {integrity: sha512-NCWDa6LGZqTuzjsGfXOBWfjS/fDIbDdmVDug+7ykVe1IKT4c1gakrvlfFYp5NhAXH/lyqLM8wsAPo5wNy73Feg==} + + '@shikijs/vscode-textmate@9.3.0': + resolution: {integrity: sha512-jn7/7ky30idSkd/O5yDBfAnVt+JJpepofP/POZ1iMOxK59cOfqIgg/Dj0eFsjOTMw+4ycJN0uhZH/Eb0bs/EUA==} '@sideway/address@4.1.4': resolution: {integrity: sha512-7vwq+rOHVWjyXxVlR76Agnvhy8I9rpzjosTESvmhNeXOXdZZB15Fl+TI9x1SiHZH5Jv2wTGduSxFDIaq0m3DUw==} @@ -3771,6 +3652,9 @@ packages: '@simplewebauthn/types@10.0.0': resolution: {integrity: sha512-SFXke7xkgPRowY2E+8djKbdEznTVnD5R6GO7GPTthpHrokLvNKw8C3lFZypTxLI7KkCfGPfhtqB3d7OVGGa9jQ==} + '@simplewebauthn/types@11.0.0': + resolution: {integrity: sha512-b2o0wC5u2rWts31dTgBkAtSNKGX0cvL6h8QedNsKmj8O4QoLFQFR3DBVBUlpyVEhYKA+mXGUaXbcOc4JdQ3HzA==} + '@sinclair/typebox@0.27.8': resolution: {integrity: sha512-+Fj43pSMwJs4KRrH/938Uf+uAELIgVBmQzg/q1YG10djyfA3TnrU8N8XzqCh/okZdszqBQTZf96idMfE5lnwTA==} @@ -3782,12 +3666,8 @@ packages: resolution: {integrity: sha512-CX6t4SYQ37lzxicAqsBtxA3OseeoVrh9cSJ5PFYam0GksYlupRfy1A+Q4aYD3zvcfECLc0zO2u+ZnR2UYKvCrw==} engines: {node: '>=14.16'} - '@sindresorhus/is@7.0.0': - resolution: {integrity: sha512-WDTlVTyvFivSOuyvMeedzg2hdoBLZ3f1uNVuEida2Rl9BrfjrIRjWA/VZIrMRLvSwJYCAlCRA3usDt1THytxWQ==} - engines: {node: '>=18'} - - '@sindresorhus/merge-streams@4.0.0': - resolution: {integrity: sha512-tlqY9xq5ukxTUZBmoOp+m61cqwQD5pHJtFY3Mn8CA8ps6yghLH/Hw8UPdqg4OLmFW3IFlcXnQNmo/dh8HzXYIQ==} + '@sindresorhus/is@7.0.1': + resolution: {integrity: sha512-QWLl2P+rsCJeofkDNIT3WFmb6NrRud1SUYW8dIhXK/46XFV8Q/g7Bsvib0Askb0reRLe+WYPeeE+l5cH7SlkuQ==} engines: {node: '>=18'} '@sinonjs/commons@2.0.0': @@ -4038,125 +3918,120 @@ packages: '@sqltools/formatter@1.2.5': resolution: {integrity: sha512-Uy0+khmZqUrUGm5dmMqVlnvufZRSK0FbYzVgp0UMstm+F5+W2/jnEEQyc9vo1ZR/E5ZI/B1WjjoTqBqwJL6Krw==} - '@storybook/addon-actions@8.3.3': - resolution: {integrity: sha512-cbpksmld7iADwDGXgojZ4r8LGI3YA3NP68duAHg2n1dtnx1oUaFK5wd6dbNuz7GdjyhIOIy3OKU1dAuylYNGOQ==} + '@storybook/addon-actions@8.4.4': + resolution: {integrity: sha512-+Dd6alcieS6UN7IKhXLuhyQYQMu9HG/Tdr790a4EOQKpJM1NxIMuPuUH3fAoKfa9VhtI1BxTBr7zNtzg9Akqhg==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-backgrounds@8.3.3': - resolution: {integrity: sha512-aX0OIrtjIB7UgSaiv20SFkfC1iWwJIGMPsPSJ5ZPhXIIOWIEBtSujh8YXwjDEXSC4DOHalmeT4bitRRe5KrVKA==} + '@storybook/addon-backgrounds@8.4.4': + resolution: {integrity: sha512-asaGD4ruIPFthyhpByQSJagvtNN7EGKdHj5yMnsMvkSXnN0r1uVkI2/Z37hmLt02Qbzf6OQiBPW5TDL+X+EEBg==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-controls@8.3.3': - resolution: {integrity: sha512-78xRtVpY7eX/Lti00JLgwYCBRB6ZcvzY3SWk0uQjEqcTnQGoQkVg2L7oWFDlDoA1LBY18P5ei2vu8MYT9GXU4g==} + '@storybook/addon-controls@8.4.4': + resolution: {integrity: sha512-FbZRbwJQggLz6M3zB6scCp1SDGwQ5zdiD6sjBilZzgGO5rBFqG0A8PoOyr4iPrLU2y/NZBdRrJBD+6MkaJ+yzw==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-docs@8.3.3': - resolution: {integrity: sha512-REUandqq1RnMNOhsocRwx5q2fdlBAYPTDFlKASYfEn4Ln5NgbQRGxOAWl7yXAAFzbDmUDU7K20hkauecF0tyMw==} + '@storybook/addon-docs@8.4.4': + resolution: {integrity: sha512-wuHaStfpd2rkAN5Lf0qmvE3JKTHTEDbnAMTvfs9inzGBL0iAwBLjW48/ll7lLkJ2E3k/FQtaevNpuc7C52u1Bw==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-essentials@8.3.3': - resolution: {integrity: sha512-E/uXoUYcg8ulG3lVbsEKb4v5hnMeGkq9YJqiZYKgVK7iRFa6p4HeVB1wU1adnm7RgjWvh+p0vQRo4KL2CTNXqw==} + '@storybook/addon-essentials@8.4.4': + resolution: {integrity: sha512-0ObUQ98zZkeWqP2k3Un5jny3WxT3THgUKZUGD+mR8eq6CuTmJ3bUXWzDHreuDxQwgr8s5f04XD8IcRvjZ9IRgA==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-highlight@8.3.3': - resolution: {integrity: sha512-MB084xJM66rLU+iFFk34kjLUiAWzDiy6Kz4uZRa1CnNqEK0sdI8HaoQGgOxTIa2xgJor05/8/mlYlMkP/0INsQ==} + '@storybook/addon-highlight@8.4.4': + resolution: {integrity: sha512-k7EUxiMe8RCasmgfa6ZKx7UG6kU9RooTYGwqY5TG5xAQOzDwKn4qom+OYkT/9/6lORhJrUe2GgQLCrq/WGpS1A==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-interactions@8.3.3': - resolution: {integrity: sha512-3w5tpCGYdF33wF44xEhTS3Zmcwd6nITtwy5q+PJvHCJAm3fpjzL3xrjtlHKDvXNwYacJPRCbWKn2QwtxZIdN0g==} + '@storybook/addon-interactions@8.4.4': + resolution: {integrity: sha512-izqcc6tY0BiKW7DYrEnoXUEH9FYDPWNfQnqqE0nVBv3BS2DoNmm8M9SB8fZx7pPfw53cMJBGt3vrlY0Wtxy1+Q==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-links@8.3.3': - resolution: {integrity: sha512-rz4KEbzr1ca4zZEZwbOnhKiaEsokCl1KkngxT/C1YIkpW908j/kg2nnIb5MrtlAW1nirXguAR74t6CGntvdU9w==} + '@storybook/addon-links@8.4.4': + resolution: {integrity: sha512-hqTv06fPq9k5GUZD8JR49ANw5sBg8EYAsuCNoSd9OwVSBO/3y53HrMA0NCILUK8hnupPvtBuKXXoHmHes9R+1g==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.3 + storybook: ^8.4.4 peerDependenciesMeta: react: optional: true - '@storybook/addon-mdx-gfm@8.3.3': - resolution: {integrity: sha512-jdwVXoBSEdmuw8L4MxUeJ/qIInADfCwdtShnfTQIJBBRucOl8ykgfTKKNjllT79TFiK0gsWoiZmE05P4wuBofw==} + '@storybook/addon-mdx-gfm@8.4.4': + resolution: {integrity: sha512-dj98NGWowhSwWYn2LUaLMxHNvBY+73n9CFsELrttg24nOxmeRfku0uh2hp5epMmRMX3Fej7nCkKNJaU1fihZ+Q==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-measure@8.3.3': - resolution: {integrity: sha512-R20Z83gnxDRrocES344dw1Of/zDhe3XHSM6TLq80UQTJ9PhnMI+wYHQlK9DsdP3KiRkI+pQA6GCOp0s2ZRy5dg==} + '@storybook/addon-measure@8.4.4': + resolution: {integrity: sha512-KsjrwrXwrI+z7hKKfjyY1w1b0gLSLZmp15vIRJMELybWV0+4bZFLJGwMBOQFx+aWBED8yZrRV9OjTmoczawsZg==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-outline@8.3.3': - resolution: {integrity: sha512-OwqYfieNuqSqWNtUZLu3UmsfQNnwA2UaSMBZyeC2Dte9Jd59PPYggcWmH+b0S6OTbYXWNAUK5U6WdK+X9Ypzdw==} + '@storybook/addon-outline@8.4.4': + resolution: {integrity: sha512-CVS1dm6BNUWKGrJj9E1ThBp5Khe6Yw+Hhz6OFxrPZfoTr6RstwoTmvSpKjDUCn8zj6ujoORdiQUh1FsHOxAPBg==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-storysource@8.3.3': - resolution: {integrity: sha512-yPYQH9NepSNxoSsV9E7OV3/EVFrbU/r2B3E5WP/mCfqTXPg/5noce7iRi+rWqcVM1tsN1qPnSjfQQc7noF0h0Q==} + '@storybook/addon-storysource@8.4.4': + resolution: {integrity: sha512-BuMQMQvYqiaosbGkUxDPU2nfZtI2E/zxpNaubpUAH2j+bx4zdXRXyW1P71wj5GZC84bszoyXhdd++9A0knmaYA==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-toolbars@8.3.3': - resolution: {integrity: sha512-4WyiVqDm4hlJdENIVQg9pLNLdfhnNKa+haerYYSzTVjzYrUx0X6Bxafshq+sud6aRtSYU14abwP56lfW8hgTlA==} + '@storybook/addon-toolbars@8.4.4': + resolution: {integrity: sha512-ENPshJMDpfzOJ4Tgm1hSzQoaEmgDxCtP6C8LKk4MOd3X92MJ7p6kfb3y3R1BLg4E/g90qp6lKPFdcohS2tKCgQ==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/addon-viewport@8.3.3': - resolution: {integrity: sha512-2S+UpbKAL+z1ppzUCkixjaem2UDMkfmm/kyJ1wm3A/ofGLYi4fjMSKNRckk+7NdolXGQJjBo0RcaotUTxFIFwQ==} + '@storybook/addon-viewport@8.4.4': + resolution: {integrity: sha512-SRHJlLhf3tu7+sYNfVIYTeMegn6aiv4HGX97ZLvL76NWWBU8BntQ1LKMki7475mWiZNUFMoYYPsHlG+HU9FAtg==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/blocks@8.3.3': - resolution: {integrity: sha512-8Vsvxqstop3xfbsx3Dn1nEjyxvQUcOYd8vpxyp2YumxYO8FlXIRuYL6HAkYbcX8JexsKvCZYxor52D2vUGIKZg==} + '@storybook/blocks@8.4.4': + resolution: {integrity: sha512-LwM3guL7uWpYR1a/SY0KZjCUskTKEaS22eF7GK8iXAV5BY4KpKr6ArW4O9orK29KtFwKhDZQLcMcECsOJBVk/A==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.3 + storybook: ^8.4.4 peerDependenciesMeta: react: optional: true react-dom: optional: true - '@storybook/builder-vite@8.3.3': - resolution: {integrity: sha512-3yTXCLaB6bzhoPH3PqtacKkcaC1uV4L+IHTf1Zypx1NO1pLZHyhYf0T7dIOxTh2JZfqu1Pm9hTvOmWfR12m+9w==} + '@storybook/builder-vite@8.4.4': + resolution: {integrity: sha512-UfPzE0p2xvBK7sA853N3VN+Plfw6/DIVppwbgsaRdzie52QXZQrl60u0igD47DHi6+xbqCBWDz7up4h3k00Z5A==} peerDependencies: - '@preact/preset-vite': '*' - storybook: ^8.3.3 - typescript: '>= 4.3.x' + storybook: ^8.4.4 vite: ^4.0.0 || ^5.0.0 - vite-plugin-glimmerx: '*' - peerDependenciesMeta: - '@preact/preset-vite': - optional: true - typescript: - optional: true - vite-plugin-glimmerx: - optional: true - '@storybook/components@8.3.3': - resolution: {integrity: sha512-i2JYtesFGkdu+Hwuj+o9fLuO3yo+LPT1/8o5xBVYtEqsgDtEAyuRUWjSz8d8NPtzloGPOv5kvR6MokWDfbeMfw==} + '@storybook/components@8.4.4': + resolution: {integrity: sha512-0BSZVmsk23C0BSRKx3liZSVQFXtoF86XQFdNQxjrXIwdHIEN7TcL3DwcxeVUU5ilGp7HeDgAydGNIPGgTeEe6g==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/core-events@8.3.3': - resolution: {integrity: sha512-YL+gBuCS81qktzTkvw0MXUJW0bYAXfRzMoiLfDBTrEKZfcJOB4JAlMGmvRRar0+jygK3icD42Rl5BwWoZY6KFQ==} + '@storybook/core-events@8.4.4': + resolution: {integrity: sha512-pkwr0UU95WSJtn9Q7q5ip0x8WxerLf5z4CWonvymGu9Z0bZyMXeA+GOEt/YQIJgqI4fbTK8Jqi+suC6ibUu9oQ==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/core@8.3.3': - resolution: {integrity: sha512-pmf2bP3fzh45e56gqOuBT8sDX05hGdUKIZ/hcI84d5xmd6MeHiPW8th2v946wCHcxHzxib2/UU9vQUh+mB4VNw==} + '@storybook/core@8.4.4': + resolution: {integrity: sha512-WjTmJpsHsFCd7tQ/8jFpDWjhntauXcWYYTcEZk56Pq4miyNrrXhV0S80Gxv3Uvzk0jocgtT2AKf8rQuH2UkQEg==} + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true - '@storybook/csf-plugin@8.3.3': - resolution: {integrity: sha512-7AD7ojpXr3THqpTcEI4K7oKUfSwt1hummgL/cASuQvEPOwAZCVZl2gpGtKxcXhtJXTkn3GMCAvlYMoe7O/1YWw==} + '@storybook/csf-plugin@8.4.4': + resolution: {integrity: sha512-4+6SUhp5sEJN9BY5RuxcFKvJbOqCzIUp9oHSSz36hkP07a4QH+SwxfEd0U7JRfmPpB63L+izywTzWhdADiAMOQ==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 '@storybook/csf@0.1.11': resolution: {integrity: sha512-dHYFQH3mA+EtnCkHXzicbLgsvzYjcDJ1JWsogbItZogkPHgSJM/Wr71uMkcvw8v9mmCyP4NpXJuu6bPoVsOnzg==} @@ -4171,45 +4046,45 @@ packages: react: ^16.8.0 || ^17.0.0 || ^18.0.0 react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 - '@storybook/instrumenter@8.3.3': - resolution: {integrity: sha512-ZiODB9EwCQkl4PBxGJjBHXRTLxcNs68ZZvR+xeMr0eMFzzlJG+trXoX5kK95oA4BFhGN+3uM0Zl3MoRjBtJTNA==} + '@storybook/instrumenter@8.4.4': + resolution: {integrity: sha512-mq/YVEZrB8jyyio2Of01rQixsQ72z8ssAhJS9ldIlK+cvERQi0VBCpH3pejPmjOB40yiKBJHNqH4HIANVhibgw==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/manager-api@8.3.3': - resolution: {integrity: sha512-Na4U+McOeVUJAR6qzJfQ6y2Qt0kUgEDUriNoAn+curpoKPTmIaZ79RAXBzIqBl31VyQKknKpZbozoRGf861YaQ==} + '@storybook/manager-api@8.4.4': + resolution: {integrity: sha512-rmNPcbEyzakEHoaecUbhkW7WWOkyZ0z7ywH4d5/s0ZuQS57Px2N+ZLVgRJwYK+YNHiJYqDf1BTln9YJ/Mt1L6Q==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/preview-api@8.3.3': - resolution: {integrity: sha512-GP2QlaF3BBQGAyo248N7549YkTQjCentsc1hUvqPnFWU4xfjkejbnFk8yLaIw0VbYbL7jfd7npBtjZ+6AnphMQ==} + '@storybook/preview-api@8.4.4': + resolution: {integrity: sha512-iZrWQcjRBqBHFdDXVxGpw6mHBZMCMYqhWXdyJ0d1S2y3PwcfOjkcXlQ1UiAenFHlA6dKrcYw8luKUQTL9bKReA==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/react-dom-shim@8.3.3': - resolution: {integrity: sha512-0dPC9K7+K5+X/bt3GwYmh+pCpisUyKVjWsI+PkzqGnWqaXFakzFakjswowIAIO1rf7wYZR591x3ehUAyL2bJiQ==} + '@storybook/react-dom-shim@8.4.4': + resolution: {integrity: sha512-kufv2FDK3kjADBo+/aKHsUn9T5E4p9IBAmCoIvXBGRDumPRds7Pt3MB4ODKlg+IumR7LMEq0jTJkn27ZRTuUmw==} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/react-vite@8.3.3': - resolution: {integrity: sha512-vzOqVaA/rv+X5J17eWKxdZztMKEKfsCSP8pNNmrqXWxK3pSlW0fAPxtn1kw3UNxGtAv71pcqvaCUtTJKqI1PYA==} + '@storybook/react-vite@8.4.4': + resolution: {integrity: sha512-NbTAY4R526hJ+gz7BFLS1HpGx3BikQDbq1BuEcaWsf/rJnygwlzeQmdPyfrfNC8R0ufIKRWUiPrPmMvrf8ZI6A==} engines: {node: '>=18.0.0'} peerDependencies: react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.3 + storybook: ^8.4.4 vite: ^4.0.0 || ^5.0.0 - '@storybook/react@8.3.3': - resolution: {integrity: sha512-fHOW/mNqI+sZWttGOE32Q+rAIbN7/Oib091cmE8usOM0z0vPNpywUBtqC2cCQH39vp19bhTsQaSsTcoBSweAHw==} + '@storybook/react@8.4.4': + resolution: {integrity: sha512-92lGnRcAI2qW6zH8GMBScyXmOS1ANI8ZuSP4ExQj+lGsCrAr7PBr0wuHy3wIn1YyAvQGPUn/mpYrmMz08c2HfA==} engines: {node: '>=18.0.0'} peerDependencies: - '@storybook/test': 8.3.3 + '@storybook/test': 8.4.4 react: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta react-dom: ^16.8.0 || ^17.0.0 || ^18.0.0 || ^19.0.0-beta - storybook: ^8.3.3 + storybook: ^8.4.4 typescript: '>= 4.2.x' peerDependenciesMeta: '@storybook/test': @@ -4217,38 +4092,38 @@ packages: typescript: optional: true - '@storybook/source-loader@8.3.3': - resolution: {integrity: sha512-NeP7l53mvnnfwi+91vtRaibZer+UJi6gkoaGRCpphL3L+3qVIXN3p41uXhAy+TahdFI2dbrWvLSNgtsvdXVaFg==} + '@storybook/source-loader@8.4.4': + resolution: {integrity: sha512-xaC23ljSEpHSMdp/VdqKd1o4Dr7x5lA2897RR6SKFRFDgkKD5Mp1UXsrcwqSZNSeXETTmVWXf8rHrz14VKkK6w==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/test@8.3.3': - resolution: {integrity: sha512-uZ8nMIovfI2ry989K2+cYAeEVD/3dpjj2+Rbmy7DiZWWVhFALfmqaTRkzZfShLmlH0TFv+rfcBPihGccBtw0FQ==} + '@storybook/test@8.4.4': + resolution: {integrity: sha512-tmJd+lxl3MC0Xdu1KW/69V8tibv98OvdopxGqfVR0x5dkRHM3sFK/tv1ZJAUeronlvRyhGySOu1tHUrMjcNqyA==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 - '@storybook/theming@8.3.3': - resolution: {integrity: sha512-gWJKetI6XJQgkrvvry4ez10+jLaGNCQKi5ygRPM9N+qrjA3BB8F2LCuFUTBuisa4l64TILDNjfwP/YTWV5+u5A==} + '@storybook/theming@8.4.4': + resolution: {integrity: sha512-iq4yt3Fx35ZV5owNC//E6G+QPV19xHHVN2Ugi3p7KOSFK3chuXX9mxZ1rfir+t+U30a5EPOEnlsY3/1LXn7aTw==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/types@8.3.3': - resolution: {integrity: sha512-wV1kupG1tfTMOXaBrtVHXuqp19vURVDqWTQX6nqkoUFD7Xb1lz/YNVeGP1uT/zJdJy42/HIyoib9JPx9h0Vx9w==} + '@storybook/types@8.4.4': + resolution: {integrity: sha512-NUeIhecJ+i2ul/u/ftV+f9gBT2cUOuLjgy1a+l0UbJd7n3wwN17vX2zrrDkrGG3dp3edr8bWMGjAN3WERJje1A==} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.2.0 || ^8.3.0-0 || ^8.4.0-0 || ^8.5.0-0 || ^8.6.0-0 - '@storybook/vue3-vite@8.3.3': - resolution: {integrity: sha512-IFcoOGlUGuUkL3rpm9UFs8FK9JX1ZdfGpLXRObVOVRhW3t+MsNLpx4Fqp3a/re6WcCC3yvHzbLXgvGcjpapkbw==} + '@storybook/vue3-vite@8.4.4': + resolution: {integrity: sha512-cyhPX16KzOWuHZCcMXqJ+k11xOvelWXmML6pSvhV0OtizDHxgesCdbSa1X1P76ZszjOlt8MJfPiSaE+XNwB0UQ==} engines: {node: '>=18.0.0'} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 vite: ^4.0.0 || ^5.0.0 - '@storybook/vue3@8.3.3': - resolution: {integrity: sha512-peu8MFGwmhpXoD3n42qG6TxeVHRhfHZ0/HW4+A6FXSB1c9w0CC4AzHs5f1w3yUvshtexNN5bkw9Q4nSVKtfU7A==} + '@storybook/vue3@8.4.4': + resolution: {integrity: sha512-HVUtE8x4nIJeCO592VNyrACMgGA6ViarRS6Faw+MWdUQXnZlwkadGusx2T++hnaalAt9VJLF5NRIcV8O7dA6Ig==} engines: {node: '>=18.0.0'} peerDependencies: - storybook: ^8.3.3 + storybook: ^8.4.4 vue: ^3.0.0 '@swc/cli@0.3.12': @@ -4274,14 +4149,8 @@ packages: cpu: [arm64] os: [darwin] - '@swc/core-darwin-arm64@1.6.6': - resolution: {integrity: sha512-5DA8NUGECcbcK1YLKJwNDKqdtTYDVnkfDU1WvQSXq/rU+bjYCLtn5gCe8/yzL7ISXA6rwqPU1RDejhbNt4ARLQ==} - engines: {node: '>=10'} - cpu: [arm64] - os: [darwin] - - '@swc/core-darwin-arm64@1.7.36': - resolution: {integrity: sha512-8vDczXzCgv3ceTPhEivlpGprN44YlrCK1nbfU9g2TrhV/Aiqi09W/eM5zLesdoM1Z3mJl492gc/8nlTkpDdusw==} + '@swc/core-darwin-arm64@1.9.2': + resolution: {integrity: sha512-nETmsCoY29krTF2PtspEgicb3tqw7Ci5sInTI03EU5zpqYbPjoPH99BVTjj0OsF53jP5MxwnLI5Hm21lUn1d6A==} engines: {node: '>=10'} cpu: [arm64] os: [darwin] @@ -4292,14 +4161,8 @@ packages: cpu: [x64] os: [darwin] - '@swc/core-darwin-x64@1.6.6': - resolution: {integrity: sha512-2nbh/RHpweNRsJiYDFk1KcX7UtaKgzzTNUjwtvK5cp0wWrpbXmPvdlWOx3yzwoiSASDFx78242JHHXCIOlEdsw==} - engines: {node: '>=10'} - cpu: [x64] - os: [darwin] - - '@swc/core-darwin-x64@1.7.36': - resolution: {integrity: sha512-Pa2Gao7+Wf5m3SsK4abKRtd48AtoUnJInvaC3d077swBfgZjbjUbQvcpdc2dOeQtWwo49rFqUZJonMsL0jnPgQ==} + '@swc/core-darwin-x64@1.9.2': + resolution: {integrity: sha512-9gD+bwBz8ZByjP6nZTXe/hzd0tySIAjpDHgkFiUrc+5zGF+rdTwhcNrzxNHJmy6mw+PW38jqII4uspFHUqqxuQ==} engines: {node: '>=10'} cpu: [x64] os: [darwin] @@ -4316,14 +4179,8 @@ packages: cpu: [arm] os: [linux] - '@swc/core-linux-arm-gnueabihf@1.6.6': - resolution: {integrity: sha512-YgytuyUfR7b0z0SRHKV+ylr83HmgnROgeT7xryEkth6JGpAEHooCspQ4RrWTU8+WKJ7aXiZlGXPgybQ4TiS+TA==} - engines: {node: '>=10'} - cpu: [arm] - os: [linux] - - '@swc/core-linux-arm-gnueabihf@1.7.36': - resolution: {integrity: sha512-3YsMWd7V+WZEjbfBnLkkz/olcRBa8nyoK0iIOnNARJBMcYaJxjkJSMZpmSojCnIVwvjA1N83CPAbUL+W+fCnHg==} + '@swc/core-linux-arm-gnueabihf@1.9.2': + resolution: {integrity: sha512-kYq8ief1Qrn+WmsTWAYo4r+Coul4dXN6cLFjiPZ29Cv5pyU+GFvSPAB4bEdMzwy99rCR0u2P10UExaeCjurjvg==} engines: {node: '>=10'} cpu: [arm] os: [linux] @@ -4334,14 +4191,8 @@ packages: cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-gnu@1.6.6': - resolution: {integrity: sha512-yGwx9fddzEE0iURqRVwKBQ4IwRHE6hNhl15WliHpi/PcYhzmYkUIpcbRXjr0dssubXAVPVnx6+jZVDSbutvnfg==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-gnu@1.7.36': - resolution: {integrity: sha512-lqM3aBB7kJazJYOwHeA5OGNLqXoQPZ/76b3dV+XcjN1GhD0CcXz6mW5PRYVin6OSN1eKrKBKJjtDA1mqADDEvw==} + '@swc/core-linux-arm64-gnu@1.9.2': + resolution: {integrity: sha512-n0W4XiXlmEIVqxt+rD3ZpkogsEWUk1jJ+i5bQNgB+1JuWh0fBE8c/blDgTQXa0GB5lTPVDZQussgdNOCnAZwiA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -4352,14 +4203,8 @@ packages: cpu: [arm64] os: [linux] - '@swc/core-linux-arm64-musl@1.6.6': - resolution: {integrity: sha512-a6fMbqzSAsS5KCxFJyg1mD5kwN3ZFO8qQLyJ75R/htZP/eCt05jrhmOI7h2n+1HjiG332jLnZ9S8lkVE5O8Nqw==} - engines: {node: '>=10'} - cpu: [arm64] - os: [linux] - - '@swc/core-linux-arm64-musl@1.7.36': - resolution: {integrity: sha512-bqei2YDzvUfG0pth5W2xJaj0eG4XWYk0d/NJ75vBX6bkIzK6dC8iuKQ41jOfUWonnrAs7rTDDJW0sTn/evvRdw==} + '@swc/core-linux-arm64-musl@1.9.2': + resolution: {integrity: sha512-8xzrOmsyCC1zrx2Wzx/h8dVsdewO1oMCwBTLc1gSJ/YllZYTb04pNm6NsVbzUX2tKddJVRgSJXV10j/NECLwpA==} engines: {node: '>=10'} cpu: [arm64] os: [linux] @@ -4370,14 +4215,8 @@ packages: cpu: [x64] os: [linux] - '@swc/core-linux-x64-gnu@1.6.6': - resolution: {integrity: sha512-hRGsUKNzzZle28YF0dYIpN0bt9PceR9LaVBq7x8+l9TAaDLFbgksSxcnU/ubTtsy+WsYSYGn+A83w3xWC0O8CQ==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-gnu@1.7.36': - resolution: {integrity: sha512-03maXTUyaBjeCxlDltmdzHje1ryQt1C4OWmmNgSSRXjLb+GNnAenwOJMSrcvHP/aNClD2pwsFCnYKDGy+sYE6w==} + '@swc/core-linux-x64-gnu@1.9.2': + resolution: {integrity: sha512-kZrNz/PjRQKcchWF6W292jk3K44EoVu1ad5w+zbS4jekIAxsM8WwQ1kd+yjUlN9jFcF8XBat5NKIs9WphJCVXg==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -4388,14 +4227,8 @@ packages: cpu: [x64] os: [linux] - '@swc/core-linux-x64-musl@1.6.6': - resolution: {integrity: sha512-NokIUtFxJDVv3LzGeEtYMTV3j2dnGKLac59luTeq36DQLZdJQawQIdTbzzWl2jE7lxxTZme+dhsVOH9LxE3ceg==} - engines: {node: '>=10'} - cpu: [x64] - os: [linux] - - '@swc/core-linux-x64-musl@1.7.36': - resolution: {integrity: sha512-XXysqLkvjtQnXm1zHqLhy00UYPv/gk5OtwR732X+piNisnEbcJBqI8Qp9O7YvLWllRcoP8IMBGDWLGdGLSpViA==} + '@swc/core-linux-x64-musl@1.9.2': + resolution: {integrity: sha512-TTIpR4rjMkhX1lnFR+PSXpaL83TrQzp9znRdp2TzYrODlUd/R20zOwSo9vFLCyH6ZoD47bccY7QeGZDYT3nlRg==} engines: {node: '>=10'} cpu: [x64] os: [linux] @@ -4406,14 +4239,8 @@ packages: cpu: [arm64] os: [win32] - '@swc/core-win32-arm64-msvc@1.6.6': - resolution: {integrity: sha512-lzYdI4qb4k1dFG26yv+9Jaq/bUMAhgs/2JsrLncGjLof86+uj74wKYCQnbzKAsq2hDtS5DqnHnl+//J+miZfGA==} - engines: {node: '>=10'} - cpu: [arm64] - os: [win32] - - '@swc/core-win32-arm64-msvc@1.7.36': - resolution: {integrity: sha512-k7+dmb13a/zPw+E4XYfPmLZFWJgcOcBRKIjYl9nQErtYsgsg3Ji6TBbsvJVETy23lNHyewZ17V5Vq6NzaG0hzg==} + '@swc/core-win32-arm64-msvc@1.9.2': + resolution: {integrity: sha512-+Eg2d4icItKC0PMjZxH7cSYFLWk0aIp94LNmOw6tPq0e69ax6oh10upeq0D1fjWsKLmOJAWEvnXlayZcijEXDw==} engines: {node: '>=10'} cpu: [arm64] os: [win32] @@ -4424,14 +4251,8 @@ packages: cpu: [ia32] os: [win32] - '@swc/core-win32-ia32-msvc@1.6.6': - resolution: {integrity: sha512-bvl7FMaXIJQ76WZU0ER4+RyfKIMGb6S2MgRkBhJOOp0i7VFx4WLOnrmMzaeoPJaJSkityVKAftfNh7NBzTIydQ==} - engines: {node: '>=10'} - cpu: [ia32] - os: [win32] - - '@swc/core-win32-ia32-msvc@1.7.36': - resolution: {integrity: sha512-ridD3ay6YM2PEYHZXXFN+edYEv0FOynaqOBP+NSnGNHA35azItIjoIe+KNi4WltGtAjpKCHSpjGCNfna12wdYQ==} + '@swc/core-win32-ia32-msvc@1.9.2': + resolution: {integrity: sha512-nLWBi4vZDdM/LkiQmPCakof8Dh1/t5EM7eudue04V1lIcqx9YHVRS3KMwEaCoHLGg0c312Wm4YgrWQd9vwZ5zQ==} engines: {node: '>=10'} cpu: [ia32] os: [win32] @@ -4442,29 +4263,14 @@ packages: cpu: [x64] os: [win32] - '@swc/core-win32-x64-msvc@1.6.6': - resolution: {integrity: sha512-WAP0JoCTfgeYKgOeYJoJV4ZS0sQUmU3OwvXa2dYYtMLF7zsNqOiW4niU7QlThBHgUv/qNZm2p6ITEgh3w1cltw==} - engines: {node: '>=10'} - cpu: [x64] - os: [win32] - - '@swc/core-win32-x64-msvc@1.7.36': - resolution: {integrity: sha512-j1z2Z1Ln9d0E3dHsPkC1K9XDh0ojhRPwV+GfRTu4D61PE+aYhYLvbJC6xPvL4/204QrStRS7eDu3m+BcDp3rgQ==} + '@swc/core-win32-x64-msvc@1.9.2': + resolution: {integrity: sha512-ik/k+JjRJBFkXARukdU82tSVx0CbExFQoQ78qTO682esbYXzjdB5eLVkoUbwen299pnfr88Kn4kyIqFPTje8Xw==} engines: {node: '>=10'} cpu: [x64] os: [win32] - '@swc/core@1.6.6': - resolution: {integrity: sha512-sHfmIUPUXNrQTwFMVCY5V5Ena2GTOeaWjS2GFUpjLhAgVfP90OP67DWow7+cYrfFtqBdILHuWnjkTcd0+uPKlg==} - engines: {node: '>=10'} - peerDependencies: - '@swc/helpers': '*' - peerDependenciesMeta: - '@swc/helpers': - optional: true - - '@swc/core@1.7.36': - resolution: {integrity: sha512-bu7ymMX+LCJOSSrKank25Jaq66ymLVA9fOUuy4ck3/6rbXdLw+pIJPnIDKQ9uNcxww8KDxOuJk9Ui9pqR+aGFw==} + '@swc/core@1.9.2': + resolution: {integrity: sha512-dYyEkO6mRYtZFpnOsnYzv9rY69fHAHoawYOjGOEcxk9WYtaJhowMdP/w6NcOKnz2G7GlZaenjkzkMa6ZeQeMsg==} engines: {node: '>=10'} peerDependencies: '@swc/helpers': '*' @@ -4475,17 +4281,14 @@ packages: '@swc/counter@0.1.3': resolution: {integrity: sha512-e2BR4lsJkkRlKZ/qCHPw9ZaSxc0MVUd7gtbtaB7aMvHeJVYe8sOB8DBZkP2DtISHGSku9sCK6T6cnY0CtXrOCQ==} - '@swc/jest@0.2.36': - resolution: {integrity: sha512-8X80dp81ugxs4a11z1ka43FPhP+/e+mJNXJSxiNYk8gIX/jPBtY4gQTrKu/KIoco8bzKuPI5lUxjfLiGsfvnlw==} + '@swc/jest@0.2.37': + resolution: {integrity: sha512-CR2BHhmXKGxTiFr21DYPRHQunLkX3mNIFGFkxBGji6r9uyIR5zftTOVYj1e0sFNMV2H7mf/+vpaglqaryBtqfQ==} engines: {npm: '>= 7.0.0'} peerDependencies: '@swc/core': '*' - '@swc/types@0.1.13': - resolution: {integrity: sha512-JL7eeCk6zWCbiYQg2xQSdLXQJl8Qoc9rXmG2cEKvHe3CKwMHwHGpfOb8frzNLmbycOo6I51qxnLnn9ESf4I20Q==} - - '@swc/types@0.1.9': - resolution: {integrity: sha512-qKnCno++jzcJ4lM4NTfYifm1EFSCeIfKiAHAfkENZAV5Kl9PjJIyd2yeeVv6c/2CckuLyv2NmRC5pv6pm2WQBg==} + '@swc/types@0.1.17': + resolution: {integrity: sha512-V5gRru+aD8YVyCOMAjMpWR1Ui577DD5KSJsHP8RAxopAH22jFz6GZd/qxqjO6MJHQhcsjvjOFXyDhyLQUnMveQ==} '@swc/wasm@1.2.130': resolution: {integrity: sha512-rNcJsBxS70+pv8YUWwf5fRlWX6JoY/HJc25HD/F8m6Kv7XhJdqPPMhyX6TKkUBPAG7TWlZYoxa+rHAjPy4Cj3Q==} @@ -4555,8 +4358,8 @@ packages: '@types/accepts@1.3.7': resolution: {integrity: sha512-Pay9fq2lM2wXPWbteBsRAGiWH2hig4ZE2asK+mm7kUzlxRTfL961rj89I6zV/E3PcIkDqyuBEcMxFT7rccugeQ==} - '@types/archiver@6.0.2': - resolution: {integrity: sha512-KmROQqbQzKGuaAbmK+ZcytkJ51+YqDa7NmbXjmtC5YBLSyQYo21YaUnQ3HbaPFKL1ooo6RQ6OPYPIDyxfpDDXw==} + '@types/archiver@6.0.3': + resolution: {integrity: sha512-a6wUll6k3zX6qs5KlxIggs1P1JcYJaTCx2gnlr+f0S1yd2DoaEwoIK10HmBaLnZwWneBz+JBm0dwcZu0zECBcQ==} '@types/argparse@1.0.38': resolution: {integrity: sha512-ebDJ9b0e702Yr7pWgB0jzm+CX4Srzz8RcXtLJDJB+BSccqMa36uyH/zUsSYao5+BD1ytv3k3rPYCq4mAE1hsXA==} @@ -4588,8 +4391,11 @@ packages: '@types/cacheable-request@6.0.3': resolution: {integrity: sha512-IQ3EbTzGxIigb1I3qPZc1rWJnH0BmSKv5QYTalEwweFvyBDLSAe24zP0le/hyi7ecGfZVlIVAg4BZqb8WBwKqw==} - '@types/color-convert@2.0.3': - resolution: {integrity: sha512-2Q6wzrNiuEvYxVQqhh7sXM2mhIhvZR/Paq4FdsQkOMgWsCIkKvSGj8Le1/XalulrmgOzPMqNa0ix+ePY4hTrfg==} + '@types/canvas-confetti@1.6.4': + resolution: {integrity: sha512-fNyZ/Fdw/Y92X0vv7B+BD6ysHL4xVU5dJcgzgxLdGbn8O3PezZNIJpml44lKM0nsGur+o/6+NZbZeNTt00U1uA==} + + '@types/color-convert@2.0.4': + resolution: {integrity: sha512-Ub1MmDdyZ7mX//g25uBAoH/mWGd9swVbt8BseymnaE18SU4po/PjmCrHxqIIRjBo3hV/vh1KGr0eMxUhp+t+dQ==} '@types/color-name@1.1.1': resolution: {integrity: sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==} @@ -4618,15 +4424,9 @@ packages: '@types/doctrine@0.0.9': resolution: {integrity: sha512-eOIHzCUSH7SMfonMG1LsC2f8vxBFtho6NGBznK41R84YzPuvSBzrhEps33IsQiOW9+VL6NQ9DbjQJznk/S4uRA==} - '@types/escodegen@0.0.6': - resolution: {integrity: sha512-AjwI4MvWx3HAOaZqYsjKWyEObT9lcVV0Y0V8nXo6cXzN8ZiMxVhf6F3d/UNvXVGKrEzL/Dluc5p+y9GkzlTWig==} - '@types/eslint@7.29.0': resolution: {integrity: sha512-VNcvioYDH8/FxaeTKkM4/TiTwt6pBV9E3OfGmvaw8tPl0rrHCJ4Ll15HRT+pMiFAf/MLQvAzC+6RzUMEL9Ceng==} - '@types/estree@0.0.51': - resolution: {integrity: sha512-CuPgU6f3eT/XgKKPqKd/gLZV1Xmvf1a2R5POBOGQa6uv82xpls89HU5zKeVoyR8XzHd1RGNOlQlvUe3CFkjWNQ==} - '@types/estree@1.0.6': resolution: {integrity: sha512-AYnb1nQyY49te+VRAVgmzfcgjYS91mY5P0TKUDCLEM+gNnA+3T6rWITXRLYCpahpqSQbN5cE+gHpnPyXjHWxcw==} @@ -4636,17 +4436,12 @@ packages: '@types/express@4.17.17': resolution: {integrity: sha512-Q4FmmuLGBG58btUnfS1c1r/NQdlp3DMfGDGig8WhfpA2YRUtEkxAjkZb0yvplJGYdF1fsQ81iMDcH24sSCNC/Q==} - '@types/express@4.17.21': - resolution: {integrity: sha512-ejlPM315qwLpaQlQDTjPdsUFSc6ZsP4AN6AlWnogPjQ7CVi7PYF3YVz+CY3jE2pwYf7E/7HlDAN0rV2GxTG0HQ==} - - '@types/find-cache-dir@3.2.1': - resolution: {integrity: sha512-frsJrz2t/CeGifcu/6uRo4b+SzAwT4NYCVPu1GN8IB9XTzrpPkGuV0tmh9mN+/L0PklAlsC3u5Fxt0ju00LXIw==} - - '@types/fluent-ffmpeg@2.1.26': - resolution: {integrity: sha512-0JVF3wdQG+pN0ImwWD0bNgJiKF2OHg/7CDBHw5UIbRTvlnkgGHK6V5doE54ltvhud4o31/dEiHm23CAlxFiUQg==} + '@types/fluent-ffmpeg@2.1.27': + resolution: {integrity: sha512-QiDWjihpUhriISNoBi2hJBRUUmoj/BMTYcfz+F+ZM9hHWBYABFAE6hjP/TbCZC0GWwlpa3FzvHH9RzFeRusZ7A==} '@types/form-data@2.5.0': resolution: {integrity: sha512-23/wYiuckYYtFpL+4RPWiWmRQH2BjFuqCUi2+N3amB1a1Drv+i/byTrGvlLwRVLFNAZbwpbQ7JvTK+VCAPMbcg==} + deprecated: This is a stub types definition. form-data provides its own type definitions, so you do not need this installed. '@types/glob@7.2.0': resolution: {integrity: sha512-ZUxbzKl0IfJILTS6t7ip5fQQM/J3TJYubDm3nMbgubNNYS62eXeUpoLUC8/7fJNiFYHTrGPQn7hspDUzIHX3UA==} @@ -4678,8 +4473,8 @@ packages: '@types/jest@29.5.12': resolution: {integrity: sha512-eDC8bTvT/QhYdxJAulQikueigY5AsdBRH2yDKW3yveW7svY3+DzN84/2NUgkw10RTiJbWqZrTtoGVdYlvFJdLw==} - '@types/jest@29.5.13': - resolution: {integrity: sha512-wd+MVEZCHt23V0/L642O5APvspWply/rGY5BcW4SUETo2UzPU3Z26qr8jC2qxpimI2jjx9h7+2cj2FwIr01bXg==} + '@types/jest@29.5.14': + resolution: {integrity: sha512-ZN+4sdnLUbo8EVvVc2ao0GFW6oVrQRPn4K2lglySj7APvSrgzxHiNNK99us4WDMi57xxA2yggblIAMNhXOotLQ==} '@types/js-yaml@4.0.9': resolution: {integrity: sha512-k4MGaQl5TGo/iipqb2UDG2UwjXziSWkh0uysQelTlJpX1qGlpUZYm8PnO4DxG1qBomtJUdYJ6qR6xdIah10JLg==} @@ -4708,12 +4503,6 @@ packages: '@types/keyv@3.1.4': resolution: {integrity: sha512-BQ5aZNSCpj7D6K2ksrRCTmKRLEpnPvWDiLPfoGyhZ++8YtiK9d/3DBKPJgry359X/P1PfruyYwvnvwFjuEiEIg==} - '@types/lodash@4.14.191': - resolution: {integrity: sha512-BdZ5BCCvho3EIXw6wUCXHe7rS53AIDPLE+JzwgT+OsJk53oBfbSmZZ7CX4VaRoN78N+TJpFi9QPlfIVNmJYWxQ==} - - '@types/matter-js@0.19.6': - resolution: {integrity: sha512-ffk6tqJM5scla+ThXmnox+mdfCo3qYk6yMjQsNcrbo6eQ5DqorVdtnaL+1agCoYzxUjmHeiNB7poBMAmhuLY7w==} - '@types/matter-js@0.19.7': resolution: {integrity: sha512-dlh50YEh1lQS4fiCDGBnK75ocHQIq/1E371Qk6hASJImICIivdZQC2GkOqnfBm0Hac2xLk5+yrqRFDAEfj/yLA==} @@ -4741,23 +4530,11 @@ packages: '@types/ms@0.7.34': resolution: {integrity: sha512-nG96G3Wp6acyAgJqGasjODb+acrI7KltPiRxzHPXnP3NgI28bpQDRv53olbqGXbfcgF5aiiHmO3xpwEpS5Ld9g==} - '@types/mute-stream@0.0.4': - resolution: {integrity: sha512-CPM9nzrCPPJHQNA9keH9CVkVI+WR5kMa+7XEs5jcGQ0VoAGnLv242w8lIVgwAEfmE4oufJRaTc9PNLQl0ioAow==} - - '@types/mysql@2.15.22': - resolution: {integrity: sha512-wK1pzsJVVAjYCSZWQoWHziQZbNggXFDUEIGf54g4ZM/ERuP86uGdWeKZWMYlqTPMZfHJJvLPyogXGvCOg87yLQ==} - - '@types/node@20.11.5': - resolution: {integrity: sha512-g557vgQjUUfN76MZAN/dt1z3dzcUsimuysco0KeluHgrPdJXkP/XdAURgyO2W9fZWHRtRBiVKzKn8vyOAwlG+w==} - - '@types/node@20.14.12': - resolution: {integrity: sha512-r7wNXakLeSsGT0H1AU863vS2wa5wBOK4bWMjZz2wj+8nBx+m5PeIn0k8AloSLpRuiwdRQZwarZqHE4FNArPuJQ==} + '@types/mysql@2.15.26': + resolution: {integrity: sha512-DSLCOXhkvfS5WNNPbfn2KdICAmk8lLc+/PNvnPnF7gOdMZCxopXduqv0OQ13y/yA/zXTSikZZqVgybUxOEg6YQ==} - '@types/node@20.9.1': - resolution: {integrity: sha512-HhmzZh5LSJNS5O8jQKpJ/3ZcrrlG6L70hpGqMIAoM9YVD0YBRNWYsfwcXq8VnSjlNpCpgLzMXdiPo+dxcvSmiA==} - - '@types/node@22.7.5': - resolution: {integrity: sha512-jML7s2NAzMWc//QSJ1a3prpk78cOPchGvXJsC3C6R6PSMoooztvRVQEz89gmBTBY1SPMaqo5teB4uNHPdetShQ==} + '@types/node@22.9.0': + resolution: {integrity: sha512-vuyHg81vvWA1Z1ELfvLko2c8f34gyA0zaic0+Rllc5lbCnbSyuvb2Oxpm6TAUAC/2xZN3QGqxBNggD1nNR2AfQ==} '@types/nodemailer@6.4.16': resolution: {integrity: sha512-uz6hN6Pp0upXMcilM61CoKyjT7sskBoOWpptkjjJp8jIMlTdc3xG01U7proKkXzruMS4hS0zqtHNkNPFB20rKQ==} @@ -4774,14 +4551,17 @@ packages: '@types/oauth@0.9.5': resolution: {integrity: sha512-+oQ3C2Zx6ambINOcdIARF5Z3Tu3x//HipE889/fqo3sgpQZbe9c6ExdQFtN6qlhpR7p83lTZfPJt0tCAW29dog==} + '@types/oauth@0.9.6': + resolution: {integrity: sha512-H9TRCVKBNOhZZmyHLqFt9drPM9l+ShWiqqJijU1B8P3DX3ub84NjxDuy+Hjrz+fEca5Kwip3qPMKNyiLgNJtIA==} + '@types/object-assign-deep@0.4.3': resolution: {integrity: sha512-d9Gxaj5j1hzrxJ61EFEg13B4g4FgrT/DYtcDWFXPehR8DF2SUZbVMFtZIs8exkVRiqrqBpdTc/lUUZjncsPpMw==} '@types/parse-link-header@2.0.3': resolution: {integrity: sha512-ffLAxD6Xqcf2gSbtEJehj8yJ5R/2OZqD4liodQvQQ+hhO4kg1mk9ToEZQPMtNTm/zIQj2GNleQbsjPp9+UQm4Q==} - '@types/pg-pool@2.0.4': - resolution: {integrity: sha512-qZAvkv1K3QbmHHFYSNRYPkRjOWRLBYrL4B9c+wG0GSVGBw0NtJwPcgx/DSddeDJvRGMHCEQ4VMEVfuJ/0gZ3XQ==} + '@types/pg-pool@2.0.6': + resolution: {integrity: sha512-TaAUE5rq2VQYxab5Ts7WZhKNmuN78Q6PiFonTDdpbx8a1H0M1vhy3rhiMjl+e2iHmogyMw7jZF4FrE6eJUy5HQ==} '@types/pg@8.11.10': resolution: {integrity: sha512-LczQUW4dbOQzsH2RQ5qoeJ6qJPdrcM/DcMLoqWQkMLMsq83J5lAX3LXjdkWdpscFy67JSOWDnh7Ny/sPFykmkg==} @@ -4855,6 +4635,9 @@ packages: '@types/shimmer@1.0.5': resolution: {integrity: sha512-9Hp0ObzwwO57DpLFF0InUjUm/II8GmKAvzbefxQTihCb7KI6yc9yzf0nLc4mVdby5N4DRCgQM2wCup9KTieeww==} + '@types/shimmer@1.2.0': + resolution: {integrity: sha512-UE7oxhQLLd9gub6JKIAhDq06T0F6FnztwMNRvYgjeQSBeMc1ZG/tA47EwfduvkuQS8apbkM/lpLpWsaCeYsXVg==} + '@types/simple-oauth2@5.0.7': resolution: {integrity: sha512-8JbWVJbiTSBQP/7eiyGKyXWAqp3dKQZpaA+pdW16FCi32ujkzRMG8JfjoAzdWt6W8U591ZNdHcPtP2D7ILTKuA==} @@ -4876,6 +4659,9 @@ packages: '@types/statuses@2.0.4': resolution: {integrity: sha512-eqNDvZsCNY49OAXB0Firg/Sc2BgoWsntsLUdybGFOhAfCD6QJ2n9HXUIHGqt5qjrxmMv4wS8WLAw43ZkKcJ8Pw==} + '@types/tedious@4.0.14': + resolution: {integrity: sha512-KHPsfX/FoVbUGbyYvk1q9MMQHLPeRZhRJZdO45Q4YjvFkv4hMNghCWTvy7rdKessBsmtz4euWCWAB6/tVpI1Iw==} + '@types/throttle-debounce@5.0.2': resolution: {integrity: sha512-pDzSNulqooSKvSNcksnV72nk8p7gRqN8As71Sp28nov1IgmPKWbOEIwAWvBME5pPTtaXJAvG3O4oc76HlQ4kqQ==} @@ -4903,17 +4689,14 @@ packages: '@types/vary@1.1.3': resolution: {integrity: sha512-XJT8/ZQCL7NUut9QDLf6l24JfAEl7bnNdgxfj50cHIpEPRJLHHDDFOAq6i+GsEmeFfH7NamhBE4c4Thtb2egWg==} - '@types/web-push@3.6.3': - resolution: {integrity: sha512-v3oT4mMJsHeJ/rraliZ+7TbZtr5bQQuxcgD7C3/1q/zkAj29c8RE0F9lVZVu3hiQe5Z9fYcBreV7TLnfKR+4mg==} - - '@types/wrap-ansi@3.0.0': - resolution: {integrity: sha512-ltIpx+kM7g/MLRZfkbL7EsCEjfzCcScLpkg37eXEtx5kmrAKBkTJwd1GIAjDSL8wTpM6Hzn5YO4pSb91BEwu1g==} + '@types/web-push@3.6.4': + resolution: {integrity: sha512-GnJmSr40H3RAnj0s34FNTcJi1hmWFV5KXugE0mYWnYhgTAHLJ/dJKAwDmvPJYMke0RplY2XE9LnM4hqSqKIjhQ==} '@types/ws@8.5.11': resolution: {integrity: sha512-4+q7P5h3SpJxaBft0Dzpbr6lmMaqh0Jr2tbhJZ/luAwvD7ohSCniYkwz/pLxuT2h0EOa6QADgJj1Ko+TzRfZ+w==} - '@types/ws@8.5.12': - resolution: {integrity: sha512-3tPRkv1EtkDpzlgyKyI8pGsGZAGPEaXeu0DOj5DI25Ja91bdAYddYHbADRYVrZMRbfW+1l5YwXVDKohDJNQxkQ==} + '@types/ws@8.5.13': + resolution: {integrity: sha512-osM/gWBTPKgHV8XkTunnegTRIsvF6owmf5w+JtAfOw472dptdm0dlGv4xCt6GwQRcC2XVOvvRE/0bAoQcL2QkA==} '@types/yargs-parser@21.0.0': resolution: {integrity: sha512-iO9ZQHkZxHn4mSakYV0vFHAVDyEOIJQrV2uZ06HxEPcx+mt8swXoZHIbaaJ2crJYFfErySgktuTZ3BeLz+XmFA==} @@ -5101,8 +4884,8 @@ packages: '@ungap/structured-clone@1.2.0': resolution: {integrity: sha512-zuVdFrMJiuCDQUMCzQaD6KL28MjnqqN8XnAqiEq9PNm/hCPTSGfrXCOfwj1ow4LFb/tNymJPwsNbVePc1xFqrQ==} - '@vitejs/plugin-vue@5.1.4': - resolution: {integrity: sha512-N2XSI2n3sQqp5w7Y/AN/L2XDjBIRGqXko+eDp42sydYSBeJuSm5a1sLf8zakmo8u7tA8NmBgoDLA1HeOESjp9A==} + '@vitejs/plugin-vue@5.2.0': + resolution: {integrity: sha512-7n7KdUEtx/7Yl7I/WVAMZ1bEb0eVvXF3ummWTeLcs/9gvo9pJhuLdouSXGjdZ/MKD1acf1I272+X0RMua4/R3g==} engines: {node: ^18.0.0 || >=20.0.0} peerDependencies: vite: ^5.0.0 @@ -5149,44 +4932,38 @@ packages: '@volar/language-core@2.2.0': resolution: {integrity: sha512-a8WG9+4OdeNDW4ywABZIM6S6UN7em8uIlM/BZ2pWQUYrVmX+m8sj/X+QadvO+Li/t/LjAqbWJQtVgxdpEWLALQ==} - '@volar/language-core@2.4.6': - resolution: {integrity: sha512-FxUfxaB8sCqvY46YjyAAV6c3mMIq/NWQMVvJ+uS4yxr1KzOvyg61gAuOnNvgCvO4TZ7HcLExBEsWcDu4+K4E8A==} + '@volar/language-core@2.4.10': + resolution: {integrity: sha512-hG3Z13+nJmGaT+fnQzAkS0hjJRa2FCeqZt6Bd+oGNhUkQ+mTFsDETg5rqUTxyzIh5pSOGY7FHCWUS8G82AzLCA==} '@volar/source-map@2.2.0': resolution: {integrity: sha512-HQlPRlHOVqCCHK8wI76ZldHkEwKsjp7E6idUc36Ekni+KJDNrqgSqPvyHQixybXPHNU7CI9Uxd9/IkxO7LuNBw==} - '@volar/source-map@2.4.6': - resolution: {integrity: sha512-Nsh7UW2ruK+uURIPzjJgF0YRGP5CX9nQHypA2OMqdM2FKy7rh+uv3XgPnWPw30JADbKvZ5HuBzG4gSbVDYVtiw==} + '@volar/source-map@2.4.10': + resolution: {integrity: sha512-OCV+b5ihV0RF3A7vEvNyHPi4G4kFa6ukPmyVocmqm5QzOd8r5yAtiNvaPEjl8dNvgC/lj4JPryeeHLdXd62rWA==} '@volar/typescript@2.2.0': resolution: {integrity: sha512-wC6l4zLiiCLxF+FGaHCbWlQYf4vMsnRxYhcI6WgvaNppOD6r1g+Ef1RKRJUApALWU46Yy/JDU/TbdV6w/X6Liw==} - '@volar/typescript@2.4.6': - resolution: {integrity: sha512-NMIrA7y5OOqddL9VtngPWYmdQU03htNKFtAYidbYfWA0TOhyGVd9tfcP4TsLWQ+RBWDZCbBqsr8xzU0ZOxYTCQ==} - - '@vue/compiler-core@3.4.37': - resolution: {integrity: sha512-ZDDT/KiLKuCRXyzWecNzC5vTcubGz4LECAtfGPENpo0nrmqJHwuWtRLxk/Sb9RAKtR9iFflFycbkjkY+W/PZUQ==} + '@volar/typescript@2.4.10': + resolution: {integrity: sha512-F8ZtBMhSXyYKuBfGpYwqA5rsONnOwAVvjyE7KPYJ7wgZqo2roASqNWUnianOomJX5u1cxeRooHV59N0PhvEOgw==} - '@vue/compiler-core@3.5.10': - resolution: {integrity: sha512-iXWlk+Cg/ag7gLvY0SfVucU8Kh2CjysYZjhhP70w9qI4MvSox4frrP+vDGvtQuzIcgD8+sxM6lZvCtdxGunTAA==} + '@vue/compiler-core@3.5.11': + resolution: {integrity: sha512-PwAdxs7/9Hc3ieBO12tXzmTD+Ln4qhT/56S+8DvrrZ4kLDn4Z/AMUr8tXJD0axiJBS0RKIoNaR0yMuQB9v9Udg==} - '@vue/compiler-dom@3.4.37': - resolution: {integrity: sha512-rIiSmL3YrntvgYV84rekAtU/xfogMUJIclUMeIKEtVBFngOL3IeZHhsH3UaFEgB5iFGpj6IW+8YuM/2Up+vVag==} + '@vue/compiler-core@3.5.12': + resolution: {integrity: sha512-ISyBTRMmMYagUxhcpyEH0hpXRd/KqDU4ymofPgl2XAkY9ZhQ+h0ovEZJIiPop13UmR/54oA2cgMDjgroRelaEw==} - '@vue/compiler-dom@3.5.10': - resolution: {integrity: sha512-DyxHC6qPcktwYGKOIy3XqnHRrrXyWR2u91AjP+nLkADko380srsC2DC3s7Y1Rk6YfOlxOlvEQKa9XXmLI+W4ZA==} + '@vue/compiler-dom@3.5.11': + resolution: {integrity: sha512-pyGf8zdbDDRkBrEzf8p7BQlMKNNF5Fk/Cf/fQ6PiUz9at4OaUfyXW0dGJTo2Vl1f5U9jSLCNf0EZJEogLXoeew==} - '@vue/compiler-sfc@3.4.37': - resolution: {integrity: sha512-vCfetdas40Wk9aK/WWf8XcVESffsbNkBQwS5t13Y/PcfqKfIwJX2gF+82th6dOpnpbptNMlMjAny80li7TaCIg==} + '@vue/compiler-dom@3.5.12': + resolution: {integrity: sha512-9G6PbJ03uwxLHKQ3P42cMTi85lDRvGLB2rSGOiQqtXELat6uI4n8cNz9yjfVHRPIu+MsK6TE418Giruvgptckg==} - '@vue/compiler-sfc@3.5.10': - resolution: {integrity: sha512-to8E1BgpakV7224ZCm8gz1ZRSyjNCAWEplwFMWKlzCdP9DkMKhRRwt0WkCjY7jkzi/Vz3xgbpeig5Pnbly4Tow==} + '@vue/compiler-sfc@3.5.12': + resolution: {integrity: sha512-2k973OGo2JuAa5+ZlekuQJtitI5CgLMOwgl94BzMCsKZCX/xiqzJYzapl4opFogKHqwJk34vfsaKpfEhd1k5nw==} - '@vue/compiler-ssr@3.4.37': - resolution: {integrity: sha512-TyAgYBWrHlFrt4qpdACh8e9Ms6C/AZQ6A6xLJaWrCL8GCX5DxMzxyeFAEMfU/VFr4tylHm+a2NpfJpcd7+20XA==} - - '@vue/compiler-ssr@3.5.10': - resolution: {integrity: sha512-hxP4Y3KImqdtyUKXDRSxKSRkSm1H9fCvhojEYrnaoWhE4w/y8vwWhnosJoPPe2AXm5sU7CSbYYAgkt2ZPhDz+A==} + '@vue/compiler-ssr@3.5.12': + resolution: {integrity: sha512-eLwc7v6bfGBSM7wZOGPmRavSWzNFF6+PdRhE+VFJhNCgHiF8AM7ccoqcv5kBXA2eWUfigD7byekvf/JsOfKvPA==} '@vue/compiler-vue2@2.7.16': resolution: {integrity: sha512-qYC3Psj9S/mfu9uVi5WvNZIzq+xnXMhOwbTFKKDD7b1lhpnn71jXSFdTQ+WsIEk0ONCd7VV2IMm7ONl6tbQ86A==} @@ -5199,47 +4976,33 @@ packages: typescript: optional: true - '@vue/language-core@2.1.6': - resolution: {integrity: sha512-MW569cSky9R/ooKMh6xa2g1D0AtRKbL56k83dzus/bx//RDJk24RHWkMzbAlXjMdDNyxAaagKPRquBIxkxlCkg==} + '@vue/language-core@2.1.10': + resolution: {integrity: sha512-DAI289d0K3AB5TUG3xDp9OuQ71CnrujQwJrQnfuZDwo6eGNf0UoRlPuaVNO+Zrn65PC3j0oB2i7mNmVPggeGeQ==} peerDependencies: typescript: '*' peerDependenciesMeta: typescript: optional: true - '@vue/reactivity@3.4.37': - resolution: {integrity: sha512-UmdKXGx0BZ5kkxPqQr3PK3tElz6adTey4307NzZ3whZu19i5VavYal7u2FfOmAzlcDVgE8+X0HZ2LxLb/jgbYw==} - - '@vue/reactivity@3.5.10': - resolution: {integrity: sha512-kW08v06F6xPSHhid9DJ9YjOGmwNDOsJJQk0ax21wKaUYzzuJGEuoKNU2Ujux8FLMrP7CFJJKsHhXN9l2WOVi2g==} + '@vue/reactivity@3.5.12': + resolution: {integrity: sha512-UzaN3Da7xnJXdz4Okb/BGbAaomRHc3RdoWqTzlvd9+WBR5m3J39J1fGcHes7U3za0ruYn/iYy/a1euhMEHvTAg==} - '@vue/runtime-core@3.4.37': - resolution: {integrity: sha512-MNjrVoLV/sirHZoD7QAilU1Ifs7m/KJv4/84QVbE6nyAZGQNVOa1HGxaOzp9YqCG+GpLt1hNDC4RbH+KtanV7w==} + '@vue/runtime-core@3.5.12': + resolution: {integrity: sha512-hrMUYV6tpocr3TL3Ad8DqxOdpDe4zuQY4HPY3X/VRh+L2myQO8MFXPAMarIOSGNu0bFAjh1yBkMPXZBqCk62Uw==} - '@vue/runtime-core@3.5.10': - resolution: {integrity: sha512-9Q86I5Qq3swSkFfzrZ+iqEy7Vla325M7S7xc1NwKnRm/qoi1Dauz0rT6mTMmscqx4qz0EDJ1wjB+A36k7rl8mA==} + '@vue/runtime-dom@3.5.12': + resolution: {integrity: sha512-q8VFxR9A2MRfBr6/55Q3umyoN7ya836FzRXajPB6/Vvuv0zOPL+qltd9rIMzG/DbRLAIlREmnLsplEF/kotXKA==} - '@vue/runtime-dom@3.4.37': - resolution: {integrity: sha512-Mg2EwgGZqtwKrqdL/FKMF2NEaOHuH+Ks9TQn3DHKyX//hQTYOun+7Tqp1eo0P4Ds+SjltZshOSRq6VsU0baaNg==} - - '@vue/runtime-dom@3.5.10': - resolution: {integrity: sha512-t3x7ht5qF8ZRi1H4fZqFzyY2j+GTMTDxRheT+i8M9Ph0oepUxoadmbwlFwMoW7RYCpNQLpP2Yx3feKs+fyBdpA==} - - '@vue/server-renderer@3.4.37': - resolution: {integrity: sha512-jZ5FAHDR2KBq2FsRUJW6GKDOAG9lUTX8aBEGq4Vf6B/35I9fPce66BornuwmqmKgfiSlecwuOb6oeoamYMohkg==} - peerDependencies: - vue: 3.4.37 - - '@vue/server-renderer@3.5.10': - resolution: {integrity: sha512-IVE97tt2kGKwHNq9yVO0xdh1IvYfZCShvDSy46JIh5OQxP1/EXSpoDqetVmyIzL7CYOWnnmMkVqd7YK2QSWkdw==} + '@vue/server-renderer@3.5.12': + resolution: {integrity: sha512-I3QoeDDeEPZm8yR28JtY+rk880Oqmj43hreIBVTicisFTx/Dl7JpG72g/X7YF8hnQD3IFhkky5i2bPonwrTVPg==} peerDependencies: - vue: 3.5.10 + vue: 3.5.12 - '@vue/shared@3.4.37': - resolution: {integrity: sha512-nIh8P2fc3DflG8+5Uw8PT/1i17ccFn0xxN/5oE9RfV5SVnd7G0XEFRwakrnNFE/jlS95fpGXDVG5zDETS26nmg==} + '@vue/shared@3.5.11': + resolution: {integrity: sha512-W8GgysJVnFo81FthhzurdRAWP/byq3q2qIw70e0JWblzVhjgOMiC2GyovXrZTFQJnFVryYaKGP3Tc9vYzYm6PQ==} - '@vue/shared@3.5.10': - resolution: {integrity: sha512-VkkBhU97Ki+XJ0xvl4C9YJsIZ2uIlQ7HqPpZOS3m9VCvmROPaChZU6DexdMJqvz9tbgG+4EtFVrSuailUq5KGQ==} + '@vue/shared@3.5.12': + resolution: {integrity: sha512-L2RPSAwUFbgZH20etwrXyVyCBu9OxRSi8T/38QsvnkJyvq2LufW2lDCOzm7t/U9C1mkhJGWYfCuFBCmIuNivrg==} '@vue/test-utils@2.4.1': resolution: {integrity: sha512-VO8nragneNzUZUah6kOjiFmD/gwRjUauG9DROh6oaOeFwX1cZRUNHhdeogE8635cISigXFTtGLUQWx5KCb0xeg==} @@ -5268,12 +5031,6 @@ packages: resolution: {integrity: sha512-PYAthTa2m2VKxuvSD3DPC/Gy+U+sOA1LAuT8mkmRuvw+NACSaeXEQ+NHcVF7rONl6qcaxV3Uuemwawk+7+SJLw==} engines: {node: '>= 0.6'} - acorn-import-assertions@1.9.0: - resolution: {integrity: sha512-cmMwop9x+8KFhxvKrKfPYmN6/pKTYYHBqLa0DfvVZcKMJWNyWLnaqND7dx/qn66R7ewM1UX5XMaDVP5wlVTaVA==} - deprecated: package has been renamed to acorn-import-attributes - peerDependencies: - acorn: ^8 - acorn-import-attributes@1.9.5: resolution: {integrity: sha512-n02Vykv5uA3eHGM/Z2dQrcD56kL8TyDb2p1+0P83PClMnC/nc+anbQRhIOWnSq4Ke/KvDPrY3C9hDtC/A3eHnQ==} peerDependencies: @@ -5284,10 +5041,6 @@ packages: peerDependencies: acorn: ^6.0.0 || ^7.0.0 || ^8.0.0 - acorn-walk@7.2.0: - resolution: {integrity: sha512-OPdCF6GsMIP+Az+aWfAAOEt2/+iVDKE7oy6lJ098aoe59oAmK76qV6Gw60SbZ8jHuG2wH058GF4pLFbYamYrVA==} - engines: {node: '>=0.4.0'} - acorn-walk@8.3.2: resolution: {integrity: sha512-cjkyv4OtNCIeqhHrfS81QWXoCBPExR/J62oyEqepVw8WaQeSqpW2uhuLPh1m9eWhDuOo/jUXVTlifvesOWp/4A==} engines: {node: '>=0.4.0'} @@ -5297,8 +5050,8 @@ packages: engines: {node: '>=0.4.0'} hasBin: true - acorn@8.12.1: - resolution: {integrity: sha512-tcpGyI9zbizT9JbV6oYE477V6mTlXvvi0T0G3SNIYE2apm/G5huBa1+K89VGeovbg+jycCrfhl3ADxErOuO6Jg==} + acorn@8.14.0: + resolution: {integrity: sha512-cl669nCJTZBsL97OF4kUQm5g5hC2uihk0NxY3WENAC0TYdILVkAyHymAntgxGkl7K+t0cXIrH5siy5S4XkFycA==} engines: {node: '>=0.4.0'} hasBin: true @@ -5347,6 +5100,9 @@ packages: ajv@8.17.1: resolution: {integrity: sha512-B/gBuNg5SiMTrPkC+A2+cW0RszwxYmn6VYxB/inlBStS5nx6xHIt/ehKRhIMhqusl7a8LjQoZnjCs5vhwxOQ1g==} + alien-signals@0.2.2: + resolution: {integrity: sha512-cZIRkbERILsBOXTQmMrxc9hgpxglstn69zm+F1ARf4aPAzdAFYd6sBq87ErO0Fj3DV94tglcyHG5kQz9nDC/8A==} + ansi-colors@4.1.3: resolution: {integrity: sha512-/6w/C21Pm1A7aZitlI5Ni/2J6FFQN8i1Cvz3kHABAAbw93v/NlvKdVOqz7CCWz/3iv/JplRSEEZ83XION15ovw==} engines: {node: '>=6'} @@ -5635,10 +5391,6 @@ packages: bn.js@4.12.0: resolution: {integrity: sha512-c98Bf3tPniI+scsdk237ku1Dc3ujXQTSgyiPUDEOe7tRkhrqridvh8klBv0HCEso1OLOYcHuCv/cS6DNxKH+ZA==} - body-parser@1.20.2: - resolution: {integrity: sha512-ml9pReCu3M61kGlqoTm2umSXTlRTuGTx0bfYj+uIUKKYycG5NtSbeetV3faSU6R7ajOPw0g/J1PvK4qNy7s5bA==} - engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} - body-parser@1.20.3: resolution: {integrity: sha512-7rAxByjUMqQ3/bHJy7D6OGXvx/MMc4IqBn/X0fcM1QUcAItpZrBEYhWGem+tzXH90c+G01ypMcYJBO9Y30203g==} engines: {node: '>= 0.8', npm: 1.2.8000 || >= 1.4.16} @@ -5669,6 +5421,11 @@ packages: browser-assert@1.2.1: resolution: {integrity: sha512-nfulgvOR6S4gt9UKCeGJOuSGBPGiFT6oQ/2UBnvTY/5aQ1PnksW72fhZkM30DzoRRv2WpwZf1vHHEr3mtuXIWQ==} + browserslist@4.22.2: + resolution: {integrity: sha512-0UgcrvQmBDvZHFGdYUehrCNIazki7/lUP3kkoi/r3YB2amZbFM9J43ZRkJTXBUZK4gmx56+Sqk9+Vs9mwZx9+A==} + engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} + hasBin: true + browserslist@4.23.0: resolution: {integrity: sha512-QW8HiM1shhT2GuzkvklfjcKDiWFXHOeFCIA/huJPwHsslwcydgk7X+z2zXpEijP98UCY7HbubZt5J2Zgvf0CaQ==} engines: {node: ^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7} @@ -5711,8 +5468,8 @@ packages: resolution: {integrity: sha512-4T53u4PdgsXqKaIctwF8ifXlRTTmEPJ8iEPWFdGZvcf7sbwYo6FKFEX9eNNAnzFZ7EzJAQ3CJeOtCRA4rDp7Pw==} engines: {node: '>=6.14.2'} - bullmq@5.13.2: - resolution: {integrity: sha512-McGE8k3mrCvdUHdU0sHkTKDS1xr4pff+hbEKBY51wk5S6Za0gkuejYA620VQTo3Zz37E/NVWMgumwiXPQ3yZcA==} + bullmq@5.26.1: + resolution: {integrity: sha512-XuxCGFlC1PQ2i1JHQiB9dqkqKQILMwQpU7ipi+cT/dzJaoXVcS0/IByUz6SsZ3xyOQY3twPt6G7J2d5GrsJuEA==} buraha@0.0.1: resolution: {integrity: sha512-G563A0mTbzknm2jDaNxfZuNKIdeArs8T+XQN6t+KbmgnOoevXSXhKDkyf8Md/36Jrx99ikwbCag37VGe3myExQ==} @@ -5786,6 +5543,9 @@ packages: caniuse-api@3.0.0: resolution: {integrity: sha512-bsTwuIg/BZZK/vreVTYYbSWoe2F+71P7K5QGEX+pT250DZbfU1MQ5prOKpPR+LL6uWKK3KMwMCAS74QB3Um1uw==} + caniuse-lite@1.0.30001566: + resolution: {integrity: sha512-ggIhCsTxmITBAMmK8yZjEhCO5/47jKXPu6Dha/wuCS4JePVL+3uiDEBuhu2aIoT+bqTOR8L76Ip1ARL9xYsEJA==} + caniuse-lite@1.0.30001591: resolution: {integrity: sha512-PCzRMei/vXjJyL5mJtzNiUCKP59dm8Apqc3PH8gJkMnMXZGox93RbE76jHsmLwmIo6/3nsYIpJtx0O7u5PqFuQ==} @@ -5837,14 +5597,20 @@ packages: resolution: {integrity: sha512-kWWXztvZ5SBQV+eRgKFeh8q5sLuZY2+8WUIzlxWVTg+oGwY14qylx1KbKzHd8P6ZYkAg0xyIDU9JMHhyJMZ1jw==} engines: {node: '>=10'} + character-entities-html4@2.1.0: + resolution: {integrity: sha512-1v7fgQRj6hnSwFpq1Eu0ynr/CDEw0rXo2B61qXrLNdHZmPKgb7fqS1a2JwF0rISo9q77jDI8VMEHoApn8qDoZA==} + + character-entities-legacy@3.0.0: + resolution: {integrity: sha512-RpPp0asT/6ufRm//AJVwpViZbGM/MkjQFxJccQRHmISF/22NBtsHqAWmL+/pmkPWoIUJdWyeVleTl1wydHATVQ==} + character-entities@2.0.2: resolution: {integrity: sha512-shx7oQ0Awen/BRIdkjkvz54PnEEI/EjwXDSIZp86/KKdbafHh1Df/RYGBhn4hbe2+uKC9FnT5UCEdyPz3ai9hQ==} character-parser@2.2.0: resolution: {integrity: sha512-+UqJQjFEFaTAs3bNsF2j2kEN1baG/zghZbdqoYEDxGZtJo9LBzl1A+m0D4n3qKx8N2FNv8/Xp6yV9mQmBuptaw==} - chart.js@4.4.4: - resolution: {integrity: sha512-emICKGBABnxhMjUjlYRR12PmOXhJ2eJjEHL2/dZlWjxRAZT1D8xplLFq5M0tMQK8ja+wBS/tuVEJB5C6r7VxJA==} + chart.js@4.4.6: + resolution: {integrity: sha512-8Y406zevUPbbIBA/HRk33khEmQPk5+cxeflWE/2rx1NJsjVWMPw/9mSP9rxHP5eqi6LNoPBVMfZHxbwLSgldYA==} engines: {pnpm: '>=8'} chartjs-adapter-date-fns@3.0.0: @@ -5898,8 +5664,8 @@ packages: resolution: {integrity: sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==} engines: {node: '>=10'} - chromatic@11.10.4: - resolution: {integrity: sha512-nfgDpW5gQ4FtgV1lZXXfqLjONKDCh2K4vwI3dbZrtU1ObOL9THyAzpIdnK9LRcNSeisDLX+XFCryfMg1Ql2U2g==} + chromatic@11.18.1: + resolution: {integrity: sha512-hkNT9vA6K9+PnE/khhZYBnRCOm8NonaQDs7RZ8YHFo7/lh1b/x/uFMkTjWjaj/mkM6QOR/evu5VcZMtcaauSlw==} hasBin: true peerDependencies: '@chromatic-com/cypress': ^0.*.* || ^1.0.0 @@ -5914,6 +5680,10 @@ packages: resolution: {integrity: sha512-4jYS4MOAaCIStSRwiuxc4B8MYhIe676yO1sYGzARnjXkWpmzZMMYxY6zu8WYWDhSuth5zhrQ1rhNSibyyvv4/w==} engines: {node: '>=8'} + ci-info@4.1.0: + resolution: {integrity: sha512-HutrvTNsF48wnxkzERIXOe5/mlcfFcbfCmwcg6CJnizbSue78AbDt+1cgl26zwn61WFxhcPykPfZrbqjGmBb4A==} + engines: {node: '>=8'} + cjs-module-lexer@1.2.2: resolution: {integrity: sha512-cOU9usZw8/dXIXKtwa8pM0OTJQuJkxMN6w30csNRUerHfeQ5R6U3kkU/FtJeIf3M202OHfY2U8ccInBG7/xogA==} @@ -5934,10 +5704,6 @@ packages: engines: {node: '>=8.0.0', npm: '>=5.0.0'} hasBin: true - cli-spinners@2.9.2: - resolution: {integrity: sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==} - engines: {node: '>=6'} - cli-table3@0.6.3: resolution: {integrity: sha512-w5Jac5SykAeZJKntOxJCrm63Eg5/4dhMWIcuTbo9rpE+brgaSZo0RuNJZeOyMgsUdhDeojvgyQLmjI+K50ZGyg==} engines: {node: 10.* || >= 12.*} @@ -6008,6 +5774,9 @@ packages: resolution: {integrity: sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==} engines: {node: '>= 0.8'} + comma-separated-tokens@2.0.3: + resolution: {integrity: sha512-Fu4hJdvzeylCfQPp9SGWidpzrMs7tTrlu6Vb8XGaRGck8QSNZJJp538Wrb60Lax4fPwR64ViY468OIUTbRlGZg==} + commander@10.0.1: resolution: {integrity: sha512-y4Mg2tXshplEbSGzx7amzPwKKOCGuoSRP/CjEdwwk0FOGlUbq6lKuoyDZTNZkmxHdJtp54hdfY/JUrdL7Xfdug==} engines: {node: '>=14'} @@ -6039,9 +5808,6 @@ packages: resolution: {integrity: sha512-gk/Z852D2Wtb//0I+kRFNKKE9dIIVirjoqPoA1wJU+XePVXZfGeBpk45+A1rKO4Q43prqWBNY/MiIeRLbPWUaA==} engines: {node: '>=4.0.0'} - commondir@1.0.1: - resolution: {integrity: sha512-W9pAhw0ja1Edb5GVdIF1mjZw/ASI0AlShXM83UUGe2DVr5TdAPEA1OA8m/g8zWp9x6On7gqufY+FatDbC3MDQg==} - compare-versions@6.1.1: resolution: {integrity: sha512-4hm4VPpIecmlg59CHXnRDnqGplJFrbLG4aFEl5vl6cK1u76ws3LLvX7ikFnTDl5vo39sjWD6AaDPYodJp/NNHg==} @@ -6086,22 +5852,22 @@ packages: cookie-signature@1.0.6: resolution: {integrity: sha512-QADzlaHc8icV8I7vbaJXJwod9HWYp8uCqf1xa4OfNu1T7JVxQIrUgOWtHdNDtPiywmFbiS12VjotIXLrKM3orQ==} - cookie-signature@1.2.1: - resolution: {integrity: sha512-78KWk9T26NhzXtuL26cIJ8/qNHANyJ/ZYrmEXFzUmhZdjpBv+DlWlOANRTGBt48YcyslsLrj0bMLFTmXvLRCOw==} - engines: {node: '>=6.6.0'} - - cookie@0.5.0: - resolution: {integrity: sha512-YZ3GUyn/o8gfKJlnlX7g7xq4gyO6OSuhGPKaaGssGB2qgDUS0gPgtTvoyZLTt9Ab6dC4hfc9dV5arkvc/OCmrw==} - engines: {node: '>= 0.6'} - cookie@0.6.0: resolution: {integrity: sha512-U71cyTamuh1CRNCfpGY6to28lxvNwPG4Guz/EVjgf3Jmzv0vlDp1atT9eS5dDjMYHucpHbWns6Lwf3BKz6svdw==} engines: {node: '>= 0.6'} + cookie@0.7.1: + resolution: {integrity: sha512-6DnInpx7SJ2AK3+CTUE/ZM0vWTUboZCegxhC2xiIydHR9jNuTAASBrfEpHhiGOZw/nX51bHt6YQl8jsGo4y/0w==} + engines: {node: '>= 0.6'} + cookie@0.7.2: resolution: {integrity: sha512-yki5XnKuf750l50uGTllt6kKILY4nQ1eNIQatoXEByZ5dWgnKqbnqmTrBE5B4N7lrMJKQ2ytWMiTO2o0v6Ew/w==} engines: {node: '>= 0.6'} + cookie@1.0.2: + resolution: {integrity: sha512-9Kr/j4O16ISv8zBBhJoi4bXOYNTkFLOqSL3UDB0njXxCXNezjeyVrJyGOWtgfs/q2km1gwBcfH8q1yEGoMYunA==} + engines: {node: '>=18'} + core-util-is@1.0.2: resolution: {integrity: sha512-3lqz5YjWTYnW6dlDa5TLaTCcShfar1e40rmcJVwCBJC6mWlFuj0eCHIElmG1g5kyuJ/GD+8Wn4FFCcz4gJPfaQ==} @@ -6209,13 +5975,8 @@ packages: csstype@3.1.3: resolution: {integrity: sha512-M1uQkMl8rQK/szD0LNhtqxIPLpimGm8sOBwU7lLnCpSbTyY3yeU1Vc7l4KT5zT4s/yOxHH5O7tIuuLOCnLADRw==} - cypress@13.14.2: - resolution: {integrity: sha512-lsiQrN17vHMB2fnvxIrKLAjOr9bPwsNbPZNrWf99s4u+DVmCY6U+w7O3GGG9FvP4EUVYaDu+guWeNLiUzBrqvA==} - engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} - hasBin: true - - cypress@13.15.0: - resolution: {integrity: sha512-53aO7PwOfi604qzOkCSzNlWquCynLlKE/rmmpSPcziRH6LNfaDUAklQT6WJIsD8ywxlIy+uVZsnTMCCQVd2kTw==} + cypress@13.15.2: + resolution: {integrity: sha512-ARbnUorjcCM3XiPwgHKuqsyr5W9Qn+pIIBPaoilnoBkLdSC2oLQjV1BUpnmc7KR+b7Avah3Ly2RMFnfxr96E/A==} engines: {node: ^16.0.0 || ^18.0.0 || >=20.0.0} hasBin: true @@ -6503,6 +6264,9 @@ packages: engines: {node: '>=0.10.0'} hasBin: true + electron-to-chromium@1.4.601: + resolution: {integrity: sha512-SpwUMDWe9tQu8JX5QCO1+p/hChAi9AE9UpoC3rcHVc+gdCGlbT3SGb5I1klgb952HRIyvt9wZhSz9bNBYz9swA==} + electron-to-chromium@1.4.686: resolution: {integrity: sha512-3avY1B+vUzNxEgkBDpKOP8WarvUAEwpRaiCL0He5OKWEFxzaOFiq4WoZEZe7qh0ReS7DiWoHMnYoQCKxNZNzSg==} @@ -6544,10 +6308,6 @@ packages: resolution: {integrity: sha512-V0hjH4dGPh9Ao5p0MoRY6BVqtwCjhz6vI5LT8AJ55H+4g9/4vbHx1I54fS0XuclLhDHArPQCiMjDxjaL8fPxhw==} engines: {node: '>=0.12'} - entities@5.0.0: - resolution: {integrity: sha512-BeJFvFRJddxobhvEdm5GqHzRV/X+ACeuw0/BuuxsCh1EUZcAIz8+kYmBp/LrQuloy6K1f3a0M7+IhmZ7QnkISA==} - engines: {node: '>=0.12'} - env-paths@2.2.1: resolution: {integrity: sha512-+h1lkLKhZMTYjog1VEpJNG7NZJWcuc2DDk/qsqSTRRCOXiLjeQ1d1/udrUGhqMxUgAlwKNZ0cf2uqan5GLuS2A==} engines: {node: '>=6'} @@ -6577,9 +6337,6 @@ packages: es-get-iterator@1.1.3: resolution: {integrity: sha512-sPZmqHBe6JIiTfN5q2pEi//TwxmAFHwj/XEuYjTuse78i8KxaqMTTzxPoFKuzRpDpTJ+0NAbpfenkmH2rePtuw==} - es-module-lexer@1.5.4: - resolution: {integrity: sha512-MVNK56NiMrOwitFB7cqDwq0CQutbw+0BvLshJSse0MUNU+y1FC3bUS/AQg7oUng+/wKrrki7JfmwtVHkVfPLlw==} - es-object-atoms@1.0.0: resolution: {integrity: sha512-MZ4iQ6JwHOBQjahnjwaC1ZtIBH+2ohjamzAO3oaHcXYup7qxjF2fixyH+Q71voWHeOkI2q/TnJao/KfXYIZWbw==} engines: {node: '>= 0.4'} @@ -6602,6 +6359,9 @@ packages: resolution: {integrity: sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==} engines: {node: '>= 0.4'} + es-toolkit@1.27.0: + resolution: {integrity: sha512-ETSFA+ZJArcuSCpzD2TjAy6UHpx4E4uqFsoDg9F/nTLogrLmVVZQ+zNxco5h7cWnA1nNak07IXsLcaSMih+ZPQ==} + esbuild-register@3.5.0: resolution: {integrity: sha512-+4G/XmakeBAsvJuDugJvtyF1x+XJT4FMocynNpxrvEBViirpfUn2PgNpCHedfWhF4WokNsO/OvMKrmJOIJsI5A==} peerDependencies: @@ -6612,23 +6372,13 @@ packages: engines: {node: '>=12'} hasBin: true - esbuild@0.19.11: - resolution: {integrity: sha512-HJ96Hev2hX/6i5cDVwcqiJBBtuo9+FeIJOtZ9W1kA5M6AMJRHUZlpYZ1/SbEwtO0ioNAW8rUooVpC/WehY2SfA==} - engines: {node: '>=12'} - hasBin: true - esbuild@0.21.5: resolution: {integrity: sha512-mg3OPMV4hXywwpoDxu3Qda5xCKQi+vCTZq8S9J/EpkhB2HzKXq4SNFZE3+NK93JYxc8VMSep+lOUSC/RVKaBqw==} engines: {node: '>=12'} hasBin: true - esbuild@0.23.0: - resolution: {integrity: sha512-1lvV17H2bMYda/WaFb2jLPeHU3zml2k4/yagNMG8Q/YtfMjCwEUZa2eXXMgZTVSL5q1n4H7sQ0X6CdJDqqeCFA==} - engines: {node: '>=18'} - hasBin: true - - esbuild@0.23.1: - resolution: {integrity: sha512-VVNz/9Sa0bs5SELtn3f7qhJCDPCF5oMEl5cO9/SSinpE9hbPVvxbd572HH5AKiP7WD8INO53GgfDDhRjkylHEg==} + esbuild@0.24.0: + resolution: {integrity: sha512-FuLPevChGDshgSicjisSooU0cemp/sGXR841D5LHMB7mTVOmsEHcAxaH3irL53+8YDIeVNQEySh4DaYU/iuPqQ==} engines: {node: '>=18'} hasBin: true @@ -6662,11 +6412,6 @@ packages: resolution: {integrity: sha512-/veY75JbMK4j1yjvuUxuVsiS/hr/4iHs9FTT6cgTexxdE0Ly/glccBAkloH/DofkjRbZU3bnoj38mOmhkZ0lHw==} engines: {node: '>=12'} - escodegen@2.1.0: - resolution: {integrity: sha512-2NlIDTwUWJN0mRPQOdtQBzbUHvdGY2P1VXSyU83Q3xKxM7WHX2Ql8dKq782Q9TgQUNOLEzEYu9bzLNj1q88I5w==} - engines: {node: '>=6.0'} - hasBin: true - eslint-config-prettier@9.1.0: resolution: {integrity: sha512-NSWl5BFQWEPi1j4TjVNItzYV7dZXZ+wP6I6ZhrBGpChQhZRUaElihE9uRRkcbRnNb76UMKDF3r+WTmNcGPKsqw==} hasBin: true @@ -6711,14 +6456,18 @@ packages: '@typescript-eslint/parser': optional: true - eslint-plugin-vue@9.27.0: - resolution: {integrity: sha512-5Dw3yxEyuBSXTzT5/Ge1X5kIkRTQ3nvBn/VwPwInNiZBSJOO/timWMUaflONnFBzU6NhB68lxnCda7ULV5N7LA==} - engines: {node: ^14.17.0 || >=16.0.0} + eslint-plugin-import@2.31.0: + resolution: {integrity: sha512-ixmkI62Rbc2/w8Vfxyh1jQRTdRTF52VxwRVHl/ykPAmqG+Nb7/kNn+byLP0LxPgI7zWA16Jt82SybJInmMia3A==} + engines: {node: '>=4'} peerDependencies: - eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 + '@typescript-eslint/parser': '*' + eslint: ^2 || ^3 || ^4 || ^5 || ^6 || ^7.2.0 || ^8 || ^9 + peerDependenciesMeta: + '@typescript-eslint/parser': + optional: true - eslint-plugin-vue@9.28.0: - resolution: {integrity: sha512-ShrihdjIhOTxs+MfWun6oJWuk+g/LAhN+CiuOl/jjkG3l0F2AuK5NMTaWqyvBgkFtpYmyks6P4603mLmhNJW8g==} + eslint-plugin-vue@9.31.0: + resolution: {integrity: sha512-aYMUCgivhz1o4tLkRHj5oq9YgYPM4/EJc0M7TAKRLCUA5OYxRLAhYEVD2nLtTwLyixEFI+/QXSvKU9ESZFgqjQ==} engines: {node: ^14.17.0 || >=16.0.0} peerDependencies: eslint: ^6.2.0 || ^7.0.0 || ^8.0.0 || ^9.0.0 @@ -6730,30 +6479,36 @@ packages: resolution: {integrity: sha512-dOt21O7lTMhDM+X9mB4GX+DZrZtCUJPL/wlcTqxyrx5IvO0IYtILdtrQGQp+8n5S0gwSVmOf9NQrjMOgfQZlIg==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-scope@8.0.2: - resolution: {integrity: sha512-6E4xmrTw5wtxnLA5wYL3WDfhZ/1bUBGOXV0zQvVRDOtrR8D0p6W7fs3JweNYhwRYeGvd/1CKX2se0/2s7Q/nJA==} + eslint-scope@8.2.0: + resolution: {integrity: sha512-PHlWUfG6lvPc3yvP5A4PNyBL1W8fkDUccmI21JUu/+GKZBoH/W5u6usENXUrWFRsyoW5ACUjFGgAFQp5gUlb/A==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint-visitor-keys@3.4.3: resolution: {integrity: sha512-wpc+LXeiyiisxPlEkUzU6svyS1frIO3Mgxj1fdy7Pm8Ygzguax2N3Fa/D/ag1WqbOprdI+uY6wMUl8/a2G+iag==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} - eslint-visitor-keys@4.0.0: - resolution: {integrity: sha512-OtIRv/2GyiF6o/d8K7MYKKbXrOUBIK6SfkIRM4Z0dY3w+LiQ0vy3F57m0Z71bjbyeiWFiHJ8brqnmE6H6/jEuw==} + eslint-visitor-keys@4.2.0: + resolution: {integrity: sha512-UyLnSehNt62FFhSwjZlHmeokpRK59rcz29j+F1/aDgbkbRTk7wIc9XzdoasMUbRNKDM0qQt/+BJ4BrpFeABemw==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} eslint@8.57.0: resolution: {integrity: sha512-dZ6+mexnaTIbSBZWgou51U6OmzIhYM2VcNdtiTtI7qPNZm35Akpr0f6vtw3w1Kmn5PYo+tZVfh13WrhpS6oLqQ==} engines: {node: ^12.22.0 || ^14.17.0 || >=16.0.0} + deprecated: This version is no longer supported. Please see https://eslint.org/version-support for other options. hasBin: true - eslint@9.8.0: - resolution: {integrity: sha512-K8qnZ/QJzT2dLKdZJVX6W4XOwBzutMYmt0lqUS+JdXgd+HTYFlonFgkJ8s44d/zMPPCnOOk0kMWCApCPhiOy9A==} + eslint@9.14.0: + resolution: {integrity: sha512-c2FHsVBr87lnUtjP4Yhvk4yEhKrQavGafRA/Se1ouse8PfbfC/Qh9Mxa00yWsZRlqeUB9raXip0aiiUZkgnr9g==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} hasBin: true + peerDependencies: + jiti: '*' + peerDependenciesMeta: + jiti: + optional: true - espree@10.1.0: - resolution: {integrity: sha512-M1M6CpiE6ffoigIOWYO9UDP8TMUw9kqb21tf+08IgDYjCsOvCuDt4jQcZmoYxx+w7zlKw9/N0KXfto+I8/FrXA==} + espree@10.3.0: + resolution: {integrity: sha512-0QYC8b24HWY8zjRnDTL6RiHfDbAWn63qb4LMj1Z4b076A4une81+z03Kg7l7mn/48PUTqoLptSXez8oknU8Clg==} engines: {node: ^18.18.0 || ^20.9.0 || >=21.1.0} espree@9.6.1: @@ -6835,10 +6590,6 @@ packages: resolution: {integrity: sha512-VyhnebXciFV2DESc+p6B+y0LjSm0krU4OgJN44qFAhBY0TJ+1V61tYD2+wHusZ6F9n5K+vl8k0sTy7PEfV4qpg==} engines: {node: '>=16.17'} - execa@9.4.0: - resolution: {integrity: sha512-yKHlle2YGxZE842MERVIplWwNH5VYmqqcPFgtnlU//K8gxuFFXu0pwd/CrfXTumFpeEiufsP7+opT/bPJa1yVw==} - engines: {node: ^18.19.0 || >=20.5.0} - executable@4.1.1: resolution: {integrity: sha512-8iA79xD3uAch729dUG8xaaBBFGaEa0wdD2VkYLFHwlqosEj/jT66AzcreRDSgV7ehnNLBW2WR5jIXwGKjVdTLg==} engines: {node: '>=4'} @@ -6854,14 +6605,14 @@ packages: exponential-backoff@3.1.1: resolution: {integrity: sha512-dX7e/LHVJ6W3DE1MHWi9S1EYzDESENfLrYohG2G++ovZrYOkm4Knwa0mc1cn84xJOR4KEU0WSchhLbd0UklbHw==} - express@4.19.2: - resolution: {integrity: sha512-5T6nhjsT+EOMzuck8JjBHARTHfMht0POzlA60WV2pMD3gyXw2LZnZ+ueGdNxG+0calOJcWKbpFcuzLZ91YWq9Q==} - engines: {node: '>= 0.10.0'} - express@4.21.0: resolution: {integrity: sha512-VqcNGcj/Id5ZT1LZ/cfihi3ttTn+NJmkli2eZADigjq29qTlWi/hAQ43t/VLPq8+UX06FCEx3ByOYet6ZFblng==} engines: {node: '>= 0.10.0'} + express@4.21.1: + resolution: {integrity: sha512-YSFlK1Ee0/GC8QaO91tHcDxJiE/X4FbpAyQWkxAvG6AXCuR65YzK8ua6D9hvi/TzUfZMpc+BwuM1IPw8fmQBiQ==} + engines: {node: '>= 0.10.0'} + ext-list@2.2.2: resolution: {integrity: sha512-u+SQgsubraE6zItfVA0tBuCBhfU9ogSRnsvygI7wht9TS510oLkBRXBsqopeUG/GBOIQyKZO9wjTqIu/sf5zFA==} engines: {node: '>=0.10.0'} @@ -6972,10 +6723,6 @@ packages: resolution: {integrity: sha512-yaduQFRKLXYOGgEn6AZau90j3ggSOyiqXU0F9JZfeXYhNa+Jk4X+s45A2zg5jns87GAFa34BBm2kXw4XpNcbdg==} engines: {node: '>=8'} - figures@6.1.0: - resolution: {integrity: sha512-d+l3qxjSesT4V7v2fh+QnmFnUWv9lSpjarhShNTgBOfA0ttejbQUAlHLitbjkoRiDulW0OPoQPYIGhIC8ohejg==} - engines: {node: '>=18'} - file-entry-cache@6.0.1: resolution: {integrity: sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==} engines: {node: ^10.12.0 || >=12.0.0} @@ -6988,8 +6735,8 @@ packages: resolution: {integrity: sha512-hlDw5Ev+9e883s0pwUsuuYNu4tD7GgpUnOvykjv1Gya0ZIjuKumthDRua90VUn6/nlRKAjcxLUnHNTIUWwWIiw==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - file-type@19.5.0: - resolution: {integrity: sha512-dMuq6WWnP6BpQY0zYJNpTtQWgeCImSMG0BTIzUBXvxbwc1HWP/E7AE4UWU9XSCOPGJuOHda0HpDnwM2FW+d90A==} + file-type@19.6.0: + resolution: {integrity: sha512-VZR5I7k5wkD0HgFnMsq5hOsSc710MJMu5Nc5QYsbe38NN5iPV/XTObYLc/cpttRTf6lX538+5uO1ZQRhYibiZQ==} engines: {node: '>=18'} filelist@1.0.4: @@ -7011,18 +6758,10 @@ packages: resolution: {integrity: sha512-YsGpe3WHLK8ZYi4tWDg2Jy3ebRz2rXowDxnld4bkQB00cc/1Zw9AWnC0i9ztDJitivtQvaI9KaLyKrc+hBW0yg==} engines: {node: '>=8'} - finalhandler@1.2.0: - resolution: {integrity: sha512-5uXcUVftlQMFnWC9qu/svkWv3GTd2PfUhK/3PLkYNAe7FbqJMt3515HaxE6eRL74GdsriiwujiawdaB1BpEISg==} - engines: {node: '>= 0.8'} - finalhandler@1.3.1: resolution: {integrity: sha512-6BN9trH7bp3qvnrRyzsBz+g3lZxTNZTbVO2EV1CS0WIcDbawYVdYvGflME/9QP0h0pYlCDBCTjYa9nZzMDpyxQ==} engines: {node: '>= 0.8'} - find-cache-dir@3.3.2: - resolution: {integrity: sha512-wXZV5emFEjrridIgED11OoUKLxiYjAcqot/NJdAkOhlJ+vGzwhOAfcG5OX1jP+S0PcjEn8bdMJv+g2jwQ3Onig==} - engines: {node: '>=8'} - find-my-way@9.1.0: resolution: {integrity: sha512-Y5jIsuYR4BwWDYYQ2A/RWWE6gD8a0FMgtU+HOq1WKku+Cwdz8M1v8wcAmRXXM1/iqtoqg06v+LjAxMYbCjViMw==} engines: {node: '>=14'} @@ -7101,6 +6840,10 @@ packages: resolution: {integrity: sha512-ETEklSGi5t0QMZuiXoA/Q6vcnxcLQP5vdugSpuAyi6SVGi2clPPp+xgEhuMaHC+zGgn31Kd235W35f7Hykkaww==} engines: {node: '>= 6'} + form-data@4.0.1: + resolution: {integrity: sha512-tzN8e4TX8+kkxGPK8D5u0FNmjPUjw3lwC9lSLxxoB/+GtsJG91CO8bSWy73APlgAZzZbXEYZJuxjkHH2w+Ezhw==} + engines: {node: '>= 6'} + formdata-polyfill@4.0.10: resolution: {integrity: sha512-buewHzMvYL29jdeQTVILecSaZKnt/RJWjoZCF5OW60Z67/GmSLBkOFM7qh1PI3zFNtJbaZL5eQu1vLfazOwj4g==} engines: {node: '>=12.20.0'} @@ -7116,10 +6859,6 @@ packages: from@0.1.7: resolution: {integrity: sha512-twe20eF1OxVxp/ML/kq2p1uc6KvFK/+vs8WjEbeKmV2He22MKm7YF2ANIt+EOqhJ5L3K/SuuPhk0hWQDjOM23g==} - fs-extra@11.1.1: - resolution: {integrity: sha512-MGIE4HOvQCeUCzmlHs0vXpih4ysz4wg9qiSAu6cd42lVwPbTM1TjV7RusoyQqMmk/95gdQZX72u+YW+c3eEpFQ==} - engines: {node: '>=14.14'} - fs-extra@7.0.1: resolution: {integrity: sha512-YJDaCJZEnBmcbw13fvdAM9AwNOJwOzrE4pqMqBq5nFiEqXUqHwlK4B+3pUw6JNvfSPtX05xFHtYy/1ni01eGCw==} engines: {node: '>=6 <7 || >=8'} @@ -7221,9 +6960,6 @@ packages: getpass@0.1.7: resolution: {integrity: sha512-0fzj9JxOLfJ+XGLhR8ze3unN0KZCgZwiSSDz168VERjK8Wl8kVSdcu2kspd4s4wtAa1y/qrVRiAA0WclVsu0ng==} - github-slugger@2.0.0: - resolution: {integrity: sha512-IaOQ9puYtjrkq7Y0Ygl9KDZnrf/aiUJYUpVf89y8kyaxbRG7Y1SrX/jaumrv81vc61+kiMempujsM3Yw7w5qcw==} - glob-parent@5.1.2: resolution: {integrity: sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==} engines: {node: '>= 6'} @@ -7250,10 +6986,12 @@ packages: glob@7.2.3: resolution: {integrity: sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==} + deprecated: Glob versions prior to v9 are no longer supported glob@8.1.0: resolution: {integrity: sha512-r8hpEjiQEYlF2QU0df3dS+nxxSIreXQS1qRhMJM0Q5NDdR386C7jb7Hwwod8Fgiuex+k0GFjgft18yvxm5XoCQ==} engines: {node: '>=12'} + deprecated: Glob versions prior to v9 are no longer supported global-dirs@3.0.1: resolution: {integrity: sha512-NBcGGFbBA9s1VzD41QXDG+3++t9Mn5t1FpLdhESY6oKY4gYTFpX4wO3sqGUa0Srjtbfj3szX0RnemmrVRUdULA==} @@ -7271,8 +7009,8 @@ packages: resolution: {integrity: sha512-oahGvuMGQlPw/ivIYBjVSrWAfWLBeku5tpPE2fOPLi+WHffIWbuh2tCjhyQhTBPMf5E9jDEH4FOmTYgYwbKwtQ==} engines: {node: '>=18'} - globals@15.9.0: - resolution: {integrity: sha512-SmSKyLLKFbSr6rptvP8izbyxJL4ILwqO9Jg23UA0sDlGlu58V59D1//I3vlc0KJphVdUR7vMjHIplYnzBxorQA==} + globals@15.12.0: + resolution: {integrity: sha512-1+gLErljJFhbOVyaetcwJiJ4+eLe45S2E7P5UiZ9xGfeq3ATQf5DOv9G7MH3gGbKQLkzmNh2DxfZwLdw+j6oTQ==} engines: {node: '>=18'} globalthis@1.0.3: @@ -7294,8 +7032,8 @@ packages: resolution: {integrity: sha512-mThBblvlAF1d4O5oqyvN+ZxLAYwIJK7bpMxgYqPD9okW0C3qm5FFn7k811QrcuEBwaogR3ngOFoCfs6mRv7teQ==} engines: {node: '>=14.16'} - got@14.4.2: - resolution: {integrity: sha512-+Te/qEZ6hr7i+f0FNgXx/6WQteSM/QqueGvxeYQQFm0GDfoxLVJ/oiwUKYMTeioColWUTdewZ06hmrBjw6F7tw==} + got@14.4.4: + resolution: {integrity: sha512-tqiF7eSgTBwQkxb1LxsEpva8TaMYVisbhplrFVmw9GQE3855Z+MH/mnsXLLOkDxR6hZJRFMj5VTAZ8lmTF8ZOA==} engines: {node: '>=20'} graceful-fs@4.2.11: @@ -7318,8 +7056,8 @@ packages: happy-dom@10.0.3: resolution: {integrity: sha512-WkCP+Z5fX6U5PY+yHP3ElV5D9PoxRAHRWPFq3pG9rg/6Hjf5ak7dozAgSCywsTRUq2qfa8vV8OQvUy5pRXy8EQ==} - happy-dom@15.7.4: - resolution: {integrity: sha512-r1vadDYGMtsHAAsqhDuk4IpPvr6N8MGKy5ntBo7tSdim+pWDxus2PNqOcOt8LuDZ4t3KJHE+gCuzupcx/GKnyQ==} + happy-dom@15.11.4: + resolution: {integrity: sha512-AU6tzh3ADd28vSmXahgLsGyGGihXPGeKH0owDn9lhHolB6vIwEhag//T+TBzDoAcHhmVEwlxwSgtW1KZep+1MA==} engines: {node: '>=18.0.0'} hard-rejection@2.1.0: @@ -7378,14 +7116,11 @@ packages: resolution: {integrity: sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==} engines: {node: '>= 0.4'} - hast-util-heading-rank@3.0.0: - resolution: {integrity: sha512-EJKb8oMUXVHcWZTDepnr+WNbfnXKFNf9duMesmr4S8SXTJBJ9M4Yok08pu9vxdJwdlGRhVumk9mEhkEvKGifwA==} - - hast-util-is-element@3.0.0: - resolution: {integrity: sha512-Val9mnv2IWpLbNPqc/pUem+a7Ipj2aHacCwgNfTiK0vJKl0LF+4Ba4+v1oPHFpf3bLYmreq0/l3Gud9S5OH42g==} + hast-util-to-html@9.0.3: + resolution: {integrity: sha512-M17uBDzMJ9RPCqLMO92gNNUDuBSq10a25SDBI08iCCxmorf4Yy6sYHK57n9WAbRAAaU+DuR4W6GN9K4DFZesYg==} - hast-util-to-string@3.0.0: - resolution: {integrity: sha512-OGkAxX1Ua3cbcW6EJ5pT/tslVb90uViVkcJ4ZZIMW/R33DX/AkcJcRrPebPwJkHYwlDHXz4aIwvAAaAdtrACFA==} + hast-util-whitespace@3.0.0: + resolution: {integrity: sha512-88JUN06ipLwsnv+dVn+OIYOvAuvBMy/Qoi6O7mQHxdPXpjy+Cd6xRkWwux7DKO+4sYILtLBRIKgsdpS2gQc7qw==} he@1.2.0: resolution: {integrity: sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==} @@ -7422,9 +7157,8 @@ packages: html-escaper@2.0.2: resolution: {integrity: sha512-H2iMtd0I4Mt5eYiapRdIDjp+XzelXQ0tFE4JS7YFwFevXXMmOp9myNrUvCg0D6ws8iqkRPBfKHgbwig1SmlLfg==} - html-tags@3.2.0: - resolution: {integrity: sha512-vy7ClnArOZwCnqZgvv+ddgHgJiAFXe3Ge9ML5/mBctVJoUoYPCdxVucOywjDARn6CVoh3dRSFdPHy2sX80L0Wg==} - engines: {node: '>=8'} + html-void-elements@3.0.0: + resolution: {integrity: sha512-bEqo66MRXsUGxWHV5IP0PUiAWwoEjba4VCzg0LjFJBpchPaTfyfCKTG6bc5F8ucKec3q5y6qOdGyYTSBEvhCrg==} htmlescape@1.1.1: resolution: {integrity: sha512-eVcrzgbR4tim7c7soKQKtxa/kQM4TzjnlU83rcZ9bHU6t31ehfV7SktN6McWgwPWg+JYMA/O3qpGxBvFq1z2Jg==} @@ -7494,10 +7228,6 @@ packages: resolution: {integrity: sha512-AXcZb6vzzrFAUE61HnN4mpLqd/cSIwNQjtNWR0euPm6y0iqx3G4gOXaIDdtdDwZmhwe82LA6+zinmW4UBWVePQ==} engines: {node: '>=16.17.0'} - human-signals@8.0.0: - resolution: {integrity: sha512-/1/GPCpDUCCYwlERiYjxoczfP0zfvZMU/OWgQPMya9AbAE24vseigFdhAMObpc8Q4lc/kjutPfUddDYyAmejnA==} - engines: {node: '>=18.18.0'} - iconv-lite@0.4.24: resolution: {integrity: sha512-v3MXnZAcvnywkTUEZomIActle7RXXeedOR31wwl7VlyoXO4Qi9arvSenNQWne1TcRwhCL1HwLI21bEqdpj8/rA==} engines: {node: '>=0.10.0'} @@ -7530,11 +7260,8 @@ packages: resolution: {integrity: sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==} engines: {node: '>=6'} - import-in-the-middle@1.10.0: - resolution: {integrity: sha512-Z1jumVdF2GwnnYfM0a/y2ts7mZbwFMgt5rRuVmLgobgahC6iKgN5MBuXjzfTIOUpq5LSU10vJIPpVKe0X89fIw==} - - import-in-the-middle@1.7.1: - resolution: {integrity: sha512-1LrZPDtW+atAxH42S6288qyDFNQ2YCty+2mxEPRtfazH6Z5QwkaBSTS2ods7hnVJioF6rkRfNoA6A/MstpFXLg==} + import-in-the-middle@1.11.2: + resolution: {integrity: sha512-gK6Rr6EykBcc6cVWRSBR5TWf8nn6hZMYSRYqCcHa0l0d1fPK7JSYo6+Mlmck76jIX9aL/IZ71c06U2VpFwl1zA==} import-lazy@4.0.0: resolution: {integrity: sha512-rKtvo6a868b5Hu3heneU+L4yEQ4jYKLtjpnPeUdK7h0yzXGmyBTypknlkCvHFBqfX9YlorEiMM6Dnq/5atfHkw==} @@ -7559,6 +7286,7 @@ packages: inflight@1.0.6: resolution: {integrity: sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==} + deprecated: This module is not supported, and leaks memory. Do not use it. Check out lru-cache if you want a good and tested way to coalesce async requests by a key value, which is much more comprehensive and powerful. inherits@2.0.4: resolution: {integrity: sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==} @@ -7619,10 +7347,6 @@ packages: resolution: {integrity: sha512-1ANGLZ+Nkv1ptFb2pa8oG8Lem4krflKuX/gINiHJHjJUKaJHk/SXk5x6K3J+39/p0h1RQ2saROclJJ+QLvETCQ==} engines: {node: '>=8'} - is-absolute-url@4.0.1: - resolution: {integrity: sha512-/51/TKE88Lmm7Gc4/8btclNXWS+g50wXhYJq8HWIBAGUBnoAdRu1aXeh364t/O7wXDAcTJDP8PNuNKWUDWie+A==} - engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - is-arguments@1.1.1: resolution: {integrity: sha512-8Q7EARjzEnKpt/PCD7e1cgUS0a6X8u5tdSiMqXhojOdoV9TsMsiO+9VLC5vAmO8N7/GmXn7yjR8qnA6bVAEzfA==} engines: {node: '>= 0.4'} @@ -7655,10 +7379,6 @@ packages: resolution: {integrity: sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==} engines: {node: '>= 0.4'} - is-ci@3.0.1: - resolution: {integrity: sha512-ZYvCgrefwqoQ6yTyYUbQu64HsITZ3NfKX1lzaEYdkTDcfKzzCI/wthRRYKkdjHKFVgNiXKAKm65Zo1pk2as/QQ==} - hasBin: true - is-core-module@2.13.1: resolution: {integrity: sha512-hHrIjvZsftOsvKSn2TRYl63zvxsgE0K+0mYMoH6gD4omR5IWB2KynivBQczo3+wF1cCkjzvptnI9Q0sPU66ilw==} @@ -7817,10 +7537,6 @@ packages: resolution: {integrity: sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==} engines: {node: '>=10'} - is-unicode-supported@2.0.0: - resolution: {integrity: sha512-FRdAyx5lusK1iHG0TWpVtk9+1i+GjrzRffhDg4ovQ7mcidMQ6mj+MhKPmvh7Xwyv5gIS06ns49CA7Sqg7lC22Q==} - engines: {node: '>=18'} - is-weakmap@2.0.1: resolution: {integrity: sha512-NSBR4kH5oVj1Uwvv970ruUkCV7O1mzgVFO4/rev2cLRda9Tm9HrL70ZPut4rOHgY0FNrUu9BCbXA2sdQ+x0chA==} @@ -8355,10 +8071,6 @@ packages: mailcheck@1.1.1: resolution: {integrity: sha512-3WjL8+ZDouZwKlyJBMp/4LeziLFXgleOdsYu87piGcMLqhBzCsy2QFdbtAwv757TFC/rtqd738fgJw1tFQCSgA==} - make-dir@3.1.0: - resolution: {integrity: sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==} - engines: {node: '>=8'} - make-dir@4.0.0: resolution: {integrity: sha512-hXdUTZYIVOt1Ex//jAQi+wTZZpUpwBj/0QsOzqegb3rGMMeJiSEu5xLHnYfBrRV4RH2+OCSOO95Is/7x1WJ4bw==} engines: {node: '>=10'} @@ -8390,12 +8102,6 @@ packages: markdown-table@3.0.3: resolution: {integrity: sha512-Z1NL3Tb1M9wH4XESsCDEksWoKTdlUafKc4pt0GRwjUyXaCFZ+dc3g2erqB6zm3szA2IUSi7VnPI+o/9jnxh9hw==} - markdown-to-jsx@7.4.7: - resolution: {integrity: sha512-0+ls1IQZdU6cwM1yu0ZjjiVWYtkbExSyUIFU2ZeDIFuZM1W42Mh4OlJ4nb4apX4H8smxDHRdFaoIVJGwfv5hkg==} - engines: {node: '>= 10'} - peerDependencies: - react: '>= 0.14.0' - marked@4.3.0: resolution: {integrity: sha512-PRsaiG84bK+AMvxziE/lCFss8juXjNaWzVbN5tXAm4XjeaS9NAHhop+PjQxz2A9h8Q4M/xGmzP8vqNwy6JeK0A==} engines: {node: '>= 12'} @@ -8431,6 +8137,9 @@ packages: mdast-util-phrasing@4.1.0: resolution: {integrity: sha512-TqICwyvJJpBwvGAMZjj4J2n0X8QWp21b9l0o7eXyVJ25YNWYbJDVIyD1bZXE6WtV6RmKJVYmQAKWa0zWOABz2w==} + mdast-util-to-hast@13.2.0: + resolution: {integrity: sha512-QGYKEuUsYT9ykKBCMOEDLsU5JRObWQusAolFMeko/tYPufNkRffBAQjIE+99jbA87xv6FgmjLtwjh9wBWajwAA==} + mdast-util-to-markdown@2.1.0: resolution: {integrity: sha512-SR2VnIEdVNCJbP6y7kVTJgPLifdr8WEU440fQec7qHoHOUz/oJ2jmNRqdDQ3rbiStOXb2mCDGTuwsK5OPUgYlQ==} @@ -8447,8 +8156,8 @@ packages: resolution: {integrity: sha512-dq+qelQ9akHpcOl/gUVRTxVIOkAJ1wR3QAvb4RsVjS8oVoFjDGTc679wJYmUmknUF5HwMLOgb5O+a3KxfWapPQ==} engines: {node: '>= 0.6'} - meilisearch@0.42.0: - resolution: {integrity: sha512-pXaOPx/uhVGYVpejNuOcXifQVJlRVSxtvpgrGKb7ygmYo4qSNXkQXPxq1p0Tv+4/RsPJug3W04pcNnYXiqungA==} + meilisearch@0.45.0: + resolution: {integrity: sha512-+zCzEqE+CumY4icB0Vox180adZqaNtnr60hJWGiEdmol5eWmksfY8rYsTcz87styXC2ZOg+2yF56gdH6oyIBTA==} memoizerific@1.11.3: resolution: {integrity: sha512-/EuHYwAPdLtXwAwSZkh/Gutery6pD2KYd44oQLhAvQp/50mpyduZh8Q7PYHXTCJ+wuXxt7oij2LXyIJOOYFPog==} @@ -8460,9 +8169,6 @@ packages: resolution: {integrity: sha512-+obSblOQmRhcyBt62furQqRAQpNyWXo8BuQ5bN7dG8wmwQ+vwHKp/rCFD4CrTP8CsDQD1sjoZ94K417XEUk8IQ==} engines: {node: '>=10'} - merge-descriptors@1.0.1: - resolution: {integrity: sha512-cCi6g3/Zr1iqQi6ySbseM1Xvooa98N0w31jzUYrXPX2xqObmFGHJ0tQ5u74H3mVh7wLouTseZyYIq39g8cNp1w==} - merge-descriptors@1.0.3: resolution: {integrity: sha512-gaNvAS7TZ897/rVaZ0nMtAyxNyi/pdbjbAwUpFQpN70GqnVfOiXpeUUMKRBmzXaSQ8DdTX4/0ms62r2K+hE6mQ==} @@ -8736,26 +8442,16 @@ packages: resolution: {integrity: sha512-SdzXp4kD/Qf8agZ9+iTu6eql0m3kWm1A2y1hkpTeVNENutaB0BwHlSvAIaMxwntmRUAUjon2V4L8Z/njd0Ct8A==} hasBin: true - msgpackr@1.10.1: - resolution: {integrity: sha512-r5VRLv9qouXuLiIBrLpl2d5ZvPt8svdQTl5/vMvE4nzDMyEX4sgW5yWhuBBj5UmgwOTWj8CIdSXn5sAfsHAWIQ==} + msgpackr@1.11.2: + resolution: {integrity: sha512-F9UngXRlPyWCDEASDpTf6c9uNhGPTqnTeLVt7bN+bU1eajoR/8V9ys2BRaV5C/e5ihE6sJ9uPIKaYt6bFuO32g==} - msw-storybook-addon@2.0.3: - resolution: {integrity: sha512-CzHmGO32JeOPnyUnRWnB0PFTXCY1HKfHiEB/6fYoUYiFm2NYosLjzs9aBd3XJUryYEN0avJqMNh7nCRDxE5JjQ==} + msw-storybook-addon@2.0.4: + resolution: {integrity: sha512-rstO8+r01sRMg6PPP7OxM8LG5/6r4+wmp2uapHeHvm9TQQRHvpPXOU/Y9/Somysz8Oi4Ea1aummXH3JlnP2LIA==} peerDependencies: msw: ^2.0.0 - msw@2.3.4: - resolution: {integrity: sha512-sHMlwrajgmZSA2l1o7qRSe+azm/I+x9lvVVcOxAzi4vCtH8uVPJk1K5BQYDkzGl+tt0RvM9huEXXdeGrgcc79g==} - engines: {node: '>=18'} - hasBin: true - peerDependencies: - typescript: '>= 4.7.x' - peerDependenciesMeta: - typescript: - optional: true - - msw@2.4.9: - resolution: {integrity: sha512-1m8xccT6ipN4PTqLinPwmzhxQREuxaEJYdx4nIbggxP8aM7r1e71vE7RtOUSQoAm1LydjGfZKy7370XD/tsuYg==} + msw@2.6.4: + resolution: {integrity: sha512-Pm4LmWQeytDsNCR+A7gt39XAdtH6zQb6jnIKRig0FlvYOn8eksn3s1nXxUfz5KYUjbckof7Z4p2ewzgffPoCbg==} engines: {node: '>=18'} hasBin: true peerDependencies: @@ -8771,9 +8467,9 @@ packages: resolution: {integrity: sha512-WeSGziVj6+Z2/MwQo3GvqzgR+9Uc+qt8SwHKh3gvNPiISKfsMfG4SvCOFYlxxgkXt7yIV2i1yczehm0EOKIxIg==} engines: {node: '>= 6.0.0'} - mute-stream@1.0.0: - resolution: {integrity: sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==} - engines: {node: ^14.17.0 || ^16.13.0 || >=18.0.0} + mute-stream@2.0.0: + resolution: {integrity: sha512-WWdIxpyjEn+FhQJQQv9aQAYlHoNVdzIzUySNV1gHUPDSdZJ3yZn7pAAbQcV7B56Mvu881q9FZV+0Vx2xC44VWA==} + engines: {node: ^18.17.0 || >=20.5.0} mylas@2.1.13: resolution: {integrity: sha512-+MrqnJRtxdF+xngFfUUkIMQrUUL0KsxbADUkn23Z/4ibGg192Q+z+CQyiYwvWTsYjJygmMR8+w3ZDa98Zh6ESg==} @@ -8790,8 +8486,8 @@ packages: engines: {node: ^10 || ^12 || ^13.7 || ^14 || >=15.0.1} hasBin: true - nanoid@5.0.7: - resolution: {integrity: sha512-oLxFY2gd2IqnjcYyOXD8XGCftpGtZP2AbHbOkthDkvRywH5ayNtPVy9YlOPcHckXzbLTCHpkb7FB+yuxKV13pQ==} + nanoid@5.0.8: + resolution: {integrity: sha512-TcJPw+9RV9dibz1hHUzlLVy8N4X9TnwirAjrU08Juo6BNKggzVfP2ZJ/3ZUSq15Xl5i85i+Z89XBO90pB2PghQ==} engines: {node: ^18 || >=20} hasBin: true @@ -8879,15 +8575,10 @@ packages: node-releases@2.0.14: resolution: {integrity: sha512-y10wOWt8yZpqXmOgRo77WaHEmhYQYGNA6y421PKsKYWEK8aW+cqAphborZDhqfyKrbZEN92CN1X2KbafY2s7Yw==} - nodemailer@6.9.15: - resolution: {integrity: sha512-AHf04ySLC6CIfuRtRiEYtGEXgRfa6INgWGluDhnxTZhHSKvrBu7lc1VVchQ0d8nPc4cFaZoPq8vkyNoZr0TpGQ==} + nodemailer@6.9.16: + resolution: {integrity: sha512-psAuZdTIRN08HKVd/E8ObdV6NO7NTBY3KsC30F7M4H1OnmLCUNaS56FpYxyb26zWLSyYF9Ozch9KYHhHegsiOQ==} engines: {node: '>=6.0.0'} - nodemon@3.0.2: - resolution: {integrity: sha512-9qIN2LNTrEzpOPBaWHTm4Asy1LxXLSickZStAQ4IZe7zsoIpD/A7LWxhZV3t4Zu352uBcqVnRsDXSMR2Sc3lTA==} - engines: {node: '>=10'} - hasBin: true - nodemon@3.1.7: resolution: {integrity: sha512-hLj7fuMow6f0lbB0cD14Lz2xNjwsyruH251Pk4t/yIitCFJbmY1myuLlHm/q06aST4jg6EgAh74PIBBrRqpVAQ==} engines: {node: '>=10'} @@ -8946,10 +8637,6 @@ packages: resolution: {integrity: sha512-ppwTtiJZq0O/ai0z7yfudtBpWIoxM8yE6nHi1X47eFR2EWORqfbu6CnPlNsjeN683eT0qG6H/Pyf9fCcvjnnnQ==} engines: {node: ^12.20.0 || ^14.13.1 || >=16.0.0} - npm-run-path@6.0.0: - resolution: {integrity: sha512-9qny7Z9DsQU8Ou39ERsPU4OZQlSTP47ShQzuKZ6PRXpYLtIFgl/DEBYEXKlvcEa+9tHVcK8CF81Y2V72qaZhWA==} - engines: {node: '>=18'} - nth-check@2.1.1: resolution: {integrity: sha512-lqjrjmaOoAnWfMmBPL+XNnynZh2+swxiX3WUE0s4yEHI6m+AwrK2UZOimIRl3X/4QctVqS8AiZjFqyOGrMXb/w==} @@ -9037,6 +8724,9 @@ packages: resolution: {integrity: sha512-1FlR+gjXK7X+AsAHso35MnyN5KqGwJRi/31ft6x0M194ht7S+rWAvd7PHss9xSKMzE0asv1pyIHaJYq+BbacAQ==} engines: {node: '>=12'} + oniguruma-to-js@0.4.3: + resolution: {integrity: sha512-X0jWUcAlxORhOqqBREgPMgnshB7ZGYszBNspP+tS9hPD3l13CdaXcHbgImoHUHlrvGx/7AvFEkTRhAGYh+jzjQ==} + open@8.4.2: resolution: {integrity: sha512-7x81NCL719oNbsq/3mh+hVrAWmFuEYUqrq/Iw3kUzH8ReypT9QQ0BLoJS7/G9k6N81XjW4qHWtjWwe/9eLy1EQ==} engines: {node: '>=12'} @@ -9048,12 +8738,6 @@ packages: resolution: {integrity: sha512-es3mGcDXV6TKPo6n3aohzHm0qxhLyR39MhF6mkD1FwFGjhxnqMqfSIgM0eCpInZvqatve4CxmXcMZw3jnnsaXw==} hasBin: true - opentelemetry-instrumentation-fetch-node@1.2.3: - resolution: {integrity: sha512-Qb11T7KvoCevMaSeuamcLsAD+pZnavkhDnlVL0kRozfhl42dKG5Q3anUklAFKJZjY3twLR+BnRa6DlwwkIE/+A==} - engines: {node: '>18.0.0'} - peerDependencies: - '@opentelemetry/api': ^1.6.0 - optionator@0.9.4: resolution: {integrity: sha512-6IpQ7mKUxRcZNLIObR0hz7lxsapSSIYNZJwXPGeF0mTVqGKFIXj1DQcMoT22S3ROcLyY/rz0PWaWZ9ayWmad9g==} engines: {node: '>= 0.8.0'} @@ -9068,11 +8752,8 @@ packages: ospath@1.2.2: resolution: {integrity: sha512-o6E5qJV5zkAbIDNhGSIlyOhScKXgQrSRMilfph0clDfM0nEnBOlKlH4sWDmG95BW/CvwNz0vmm7dJVtU2KlMiA==} - otpauth@9.3.2: - resolution: {integrity: sha512-KixtXWN9RGdS8WHPfDo7qsOYiivCbl+VeLBT+7HBTtJebBO6aXr/bpZXr+TwY2COecdY82VeBghm31mLYQVZlQ==} - - outvariant@1.4.2: - resolution: {integrity: sha512-Ou3dJ6bA/UJ5GVHxah4LnqDwZRwAmWxrG3wtrHrbGnP4RnLCtA64A4F+ae7Y8ww660JaddSoArUR5HjipWSHAQ==} + otpauth@9.3.4: + resolution: {integrity: sha512-qXv+lpsCUO9ewitLYfeDKbLYt7UUCivnU/fwGK2OqhgrCBsRkTUNKWsgKAhkXG3aistOY+jEeuL90JEBu6W3mQ==} outvariant@1.4.3: resolution: {integrity: sha512-+Sl2UErvtsoajRDKCE5/dBz4DIvHXQQnAxtQTF04OJxY0+DyZXSo5P5Bb7XYWOh81syohlYL24hbDwxedPUJCA==} @@ -9143,10 +8824,6 @@ packages: parse-link-header@2.0.0: resolution: {integrity: sha512-xjU87V0VyHZybn2RrCX5TIFGxTVZE6zqqZWMPlIKiSKuWh/X5WZdt+w1Ki1nXB+8L/KtL+nZ4iq+sfI6MrhhMw==} - parse-ms@4.0.0: - resolution: {integrity: sha512-TXfryirbmq34y8QBwgqCVLi+8oA3oWx2eAnSn62ITyEhEYaWRlVZ2DvMM9eZbMs/RfxPu/PK/aBLyGj4IrqMHw==} - engines: {node: '>=18'} - parse-srcset@1.0.2: resolution: {integrity: sha512-/2qh0lav6CmI15FzA3i/2Bzk2zCgQhGMkvhOhKNcBVQ1ldgpbfiNTVslmooUmWJcADi1f1kIeynbDRVzNlfR6Q==} @@ -9165,8 +8842,8 @@ packages: parse5@6.0.1: resolution: {integrity: sha512-Ofn/CTFzRGTTxwpNEs9PP93gXShHcTq255nzRYSKe8AkVpZY7e1fpmTfOyoIvjP5HG7Z2ZM7VS9PPhQGW2pOpw==} - parse5@7.1.2: - resolution: {integrity: sha512-Czj1WaSVpaoj0wbhMzLmWD69anp2WH7FXMB9n1Sy8/ZFF9jolSQVMu1Ij5WIyGmcBmhk7EOndpO4mIpihVqAXw==} + parse5@7.2.1: + resolution: {integrity: sha512-BuBYQYlv1ckiPdQi/ohiivi9Sagc9JG+Ozs0r7b/0iK3sKmrb0b9FdWdBbOdx6hBCM/F9Ir82ofnBhtZOjCRPQ==} parseurl@1.3.3: resolution: {integrity: sha512-CiyeOxFT/JZyN5m0z9PfXw4SCBJ6Sygz1Dpl0wqjlhDEGGBP1GnsUVEL0p63hoG1fcj3fHynXi9NYO4nWOL+qQ==} @@ -9209,18 +8886,12 @@ packages: path-to-regexp@0.1.10: resolution: {integrity: sha512-7lf7qcQidTku0Gu3YDPc8DJ1q7OOucfa/BSsIwjuh56VU7katFvuM8hULfkwB3Fns/rsVF7PwPKVw1sl5KQS9w==} - path-to-regexp@0.1.7: - resolution: {integrity: sha512-5DFkuoqlv1uYQKxy8omFBeJPQcdoE07Kv2sferDCrAq1ohOU+MSDswDIbnx3YAM60qIOnYa53wBhXW0EbMonrQ==} - path-to-regexp@1.8.0: resolution: {integrity: sha512-n43JRhlUKUAlibEJhPeir1ncUID16QnEjNpwzNdO3Lm4ywrBpBZ5oLD0I6br9evr1Y9JTqwRtAh7JLoOzAQdVA==} path-to-regexp@3.3.0: resolution: {integrity: sha512-qyCH421YQPS2WFDxDjftfc1ZR5WKQzVzqsp4n9M2kQhVOo/ByahFoUNJfl58kOcEGfQ//7weFTDhm+ss8Ecxgw==} - path-to-regexp@6.2.1: - resolution: {integrity: sha512-JLyh7xT1kizaEvcaXOQwOc2/Yhw6KZOvPf1S8401UyLk86CU79LN3vl7ztXGm/pZ+YjoyAJ4rxmHwbkBXJX+yw==} - path-to-regexp@6.3.0: resolution: {integrity: sha512-Yhpw4T9C6hPpgPeA28us07OJeqZ5EzQTkbfwuhsUg0c237RomFoETJgmp2sa3F/41gfLE6G5cqcYwznmeEeOlQ==} @@ -9241,14 +8912,14 @@ packages: pause-stream@0.0.11: resolution: {integrity: sha512-e3FBlXLmN/D1S+zHzanP4E/4Z60oFAa3O051qt1pxa7DEJWKAyil6upYVXCWadEnuoqa4Pkc9oUx9zsxYeRv8A==} - peek-readable@5.1.3: - resolution: {integrity: sha512-kCsc9HwH5RgVA3H3VqkWFyGQwsxUxLdiSX1d5nqAm7hnMFjNFX1VhBLmJoUY0hZNc8gmDNgBkLjfhiWPsziXWA==} - engines: {node: '>=14.16'} - peek-readable@5.2.0: resolution: {integrity: sha512-U94a+eXHzct7vAd19GH3UQ2dH4Satbng0MyYTMaQatL0pvYYL5CTPR25HBhKtecl+4bfu1/i3vC6k0hydO5Vcw==} engines: {node: '>=14.16'} + peek-readable@5.3.1: + resolution: {integrity: sha512-GVlENSDW6KHaXcd9zkZltB7tCLosKB/4Hg0fqBJkAoBgYG2Tn1xtMgXtSUuMU9AK/gCm/tTdT8mgAeF4YNeeqw==} + engines: {node: '>=14.16'} + pend@1.2.0: resolution: {integrity: sha512-F3asv42UuXchdzt+xXqfW1OGlVBe+mxa2mqI0pg5yAHZPvFmY3Y6drSf/GQ1A86WgWEN9Kzh/WrgKa6iGcHXLg==} @@ -9288,8 +8959,8 @@ packages: resolution: {integrity: sha512-hRCSDuLII9/LE3smys1hRHcu5QGcLs9ggT7I/TCs0IE+2Eesxi9+9RWAAwZ0yaGjxoWICF/YHLOEjydGujoJ+g==} engines: {node: '>=10'} - pg@8.13.0: - resolution: {integrity: sha512-34wkUTh3SxTClfoHB3pQ7bIMvw9dpFU1audQQeZG837fmHfHpr14n/AELVDoOYVDW2h5RDWU78tFjkD+erSBsw==} + pg@8.13.1: + resolution: {integrity: sha512-OUir1A0rPNZlX//c7ksiu7crsGZTKSOXJPgtNiHGIlC9H0lO+NC6ZDYksSgBYY/thSWhnSRBv8w1lieNNGATNQ==} engines: {node: '>= 8.0.0'} peerDependencies: pg-native: '>=3.0.1' @@ -9313,10 +8984,17 @@ packages: picocolors@1.1.0: resolution: {integrity: sha512-TQ92mBOW0l3LeMeyLV6mzy/kWr8lkd/hp3mTg7wYK7zJhuBStmGMBG0BdeDZS/dZx1IukaX6Bk11zcln25o1Aw==} + picocolors@1.1.1: + resolution: {integrity: sha512-xceH2snhtb5M9liqDsmEw56le376mTZkEX/jEb/RxNFyegNul7eNslCXP9FDj/Lcu0X8KEyMceP2ntpaHrDEVA==} + picomatch@2.3.1: resolution: {integrity: sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==} engines: {node: '>=8.6'} + picomatch@4.0.2: + resolution: {integrity: sha512-M7BAV6Rlcy5u+m6oPhAPFgJTzAioX/6B0DxyvDlo9l8+T3nLKbrczg2WLUyzd45L8RqfUMyGPzekbMvX2Ldkwg==} + engines: {node: '>=12'} + pid-port@1.0.0: resolution: {integrity: sha512-LSNBeKChRPA4Xlrs6+zV588G1hSrFvANtPV5rt/5MPfSPK3V9XPWxx1d29svsrOjngT9ifLisXWCLS7DvO9ZhQ==} engines: {node: '>=18'} @@ -9545,14 +9223,14 @@ packages: postcss-value-parser@4.2.0: resolution: {integrity: sha512-1NNCs6uurfkVbeXG4S8JFT9t19m45ICnif8zWLd5oPSZ50QnwMfK+H3jv408d4jw/7Bttv5axS5IiHoLaVNHeQ==} - postcss@8.4.38: - resolution: {integrity: sha512-Wglpdk03BSfXkHoQa3b/oulrotAkwrlLDRSOb9D0bN86FdRyE9lppSp33aHNPgBa0JKCoB+drFLZkQoRRYae5A==} - engines: {node: ^10 || ^12 || >=14} - postcss@8.4.47: resolution: {integrity: sha512-56rxCq7G/XfB4EkXq9Egn5GCqugWvDFjafDOThIdMBsI15iqPqR5r15TfSr1YPYeEI19YeaXMCbY6u88Y76GLQ==} engines: {node: ^10 || ^12 || >=14} + postcss@8.4.49: + resolution: {integrity: sha512-OCVPnIObs4N29kxTjzLfUryOkvZEq+pf8jTF0lg8E7uETuWHA+v7j3c/xJmiqpX450191LlmZfUKkXxkTry7nA==} + engines: {node: ^10 || ^12 || >=14} + postgres-array@2.0.0: resolution: {integrity: sha512-VpZrUqU5A69eQyW2c5CA1jtLecCsN2U/bD6VilrFDWq5+5UIEVO7nazS3TEcHf1zuPYO/sqGvUvW62g86RXZuA==} engines: {node: '>=4'} @@ -9609,10 +9287,6 @@ packages: resolution: {integrity: sha512-Pdlw/oPxN+aXdmM9R00JVC9WVFoCLTKJvDVLgmJ+qAffBMxsV85l/Lu7sNx4zSzPyoL2euImuEwHhOXdEgNFZQ==} engines: {node: ^14.15.0 || ^16.10.0 || >=18.0.0} - pretty-ms@9.0.0: - resolution: {integrity: sha512-E9e9HJ9R9NasGOgPaPE8VMeiPKAyWR5jcFpNnwIejslIhWqdqOrb2wShBsncMPUb+BcCd2OPYfh7p2W6oemTng==} - engines: {node: '>=18'} - private-ip@2.3.3: resolution: {integrity: sha512-5zyFfekIVUOTVbL92hc8LJOtE/gyGHeREHkJ2yTyByP8Q2YZVoBqLg3EfYLeF0oVvGqtaEX2t2Qovja0/gStXw==} @@ -9657,8 +9331,8 @@ packages: resolution: {integrity: sha512-NxNv/kLguCA7p3jE8oL2aEBsrJWgAakBpgmgK6lpPWV+WuOmY6r2/zbAVnP+T8bQlA0nzHXSJSJW0Hq7ylaD2Q==} engines: {node: '>= 6'} - prop-types@15.8.1: - resolution: {integrity: sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==} + property-information@6.5.0: + resolution: {integrity: sha512-PgTgs/BlvHxOu8QuEN7wi5A0OmXaBcHpmCSTehcs6Uuu9IkDIEo13Hy7n898RHfrQ49vKCoGeWZSaAK01nwVig==} proto-list@1.2.4: resolution: {integrity: sha512-vtK/94akxsTMhe0/cbfpR+syPuszcuwhqVjJq26CuNDgFGj682oRBXOP5MJpv2r7JtE8MsiepGIqvvOTBwn2vA==} @@ -9752,10 +9426,6 @@ packages: engines: {node: '>=10.13.0'} hasBin: true - qs@6.11.0: - resolution: {integrity: sha512-MvjoMCJwEarSbUYk5O+nmoSzSutSsTwF85zcHPQ9OrlFoZOYIjaqBAJIqIXjptyD5vThxGq52Xu/MaJzRkIk4Q==} - engines: {node: '>=0.6'} - qs@6.13.0: resolution: {integrity: sha512-+38qI9SOr8tfZ4QmJNplMUxqjbe7LKvvZgWdExBOmd+egZTtjLB67Gu0HRX3u/XOq7UU2Nx6nsjvS16Z9uwfpg==} engines: {node: '>=0.6'} @@ -9809,12 +9479,6 @@ packages: re2@1.21.4: resolution: {integrity: sha512-MVIfXWJmsP28mRsSt8HeL750ifb8H5+oF2UDIxGaiJCr8fkMqhLZ7kcX9ADRk2dC8qeGKedB7UVYRfBVpEiLfA==} - react-colorful@5.6.1: - resolution: {integrity: sha512-1exovf0uGTGyq5mXQT0zgQ80uvj2PCwvF8zY1RN9/vbJVSjSo3fsB/4L3ObbF7u70NduSiK4xu4Y6q1MHoUGEw==} - peerDependencies: - react: '>=16.8.0' - react-dom: '>=16.8.0' - react-docgen-typescript@2.2.2: resolution: {integrity: sha512-tvg2ZtOpOi6QDwsb3GZhOjDkkX0h8Z2gipvTg6OVMUyoYoURhEiRNePT8NZItTVCDh39JJHnLdfCOkzoLbFnTg==} peerDependencies: @@ -9829,21 +9493,9 @@ packages: peerDependencies: react: ^18.3.1 - react-element-to-jsx-string@15.0.0: - resolution: {integrity: sha512-UDg4lXB6BzlobN60P8fHWVPX3Kyw8ORrTeBtClmIlGdkOOE+GYQSFvmEU5iLLpwp/6v42DINwNcwOhOLfQ//FQ==} - peerDependencies: - react: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - react-dom: ^0.14.8 || ^15.0.1 || ^16.0.0 || ^17.0.1 || ^18.0.0 - - react-is@16.13.1: - resolution: {integrity: sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==} - react-is@17.0.2: resolution: {integrity: sha512-w2GsyukL62IJnlaff/nRegPQR94C/XXamvMWmSHRJ4y7Ts/4ocGRmTHvOs8PSE6pB3dWOrD/nueuU5sduBsQ4w==} - react-is@18.1.0: - resolution: {integrity: sha512-Fl7FuabXsJnV5Q1qIOQwx/sagGF18kogb4gpfcG4gjLBWO0WDiiz1ko/ExayuxE7InyQkBLkxRFG5oxY6Uu3Kg==} - react-is@18.2.0: resolution: {integrity: sha512-xWGDIW6x921xtzPkhiULtthJHoJvBbF3q26fzloPCK0hsvxtPVelvftw3zjbHWSkR2km9Z+4uxbDDK/6Zw9B8w==} @@ -9917,6 +9569,9 @@ packages: regenerator-runtime@0.14.0: resolution: {integrity: sha512-srw17NI0TUWHuGa5CFGGmhfNIeja30WMBfbslPNhf6JrqQlLN5gcrvig1oqPxiVaXb0oW0XRKtH6Nngs5lKCIA==} + regex@4.4.0: + resolution: {integrity: sha512-uCUSuobNVeqUupowbdZub6ggI5/JZkYyJdDogddJr60L764oxC2pMZov1fQ3wM9bdyzUILDG+Sqx6NAKAz9rKQ==} + regexp.prototype.flags@1.5.0: resolution: {integrity: sha512-0SutC3pNudRKgquxGoRGIz946MZVHqbNfPjBdxeOhBrdgDKlRoXmYLQN9xRbrR09ZXWeGAdPuif7egofn6v5LA==} engines: {node: '>= 0.4'} @@ -9925,12 +9580,6 @@ packages: resolution: {integrity: sha512-vqlC04+RQoFalODCbCumG2xIOvapzVMHwsyIGM/SIE8fRhFFsXeH8/QQ+s0T0kDAhKc4k30s73/0ydkHQz6HlQ==} engines: {node: '>= 0.4'} - rehype-external-links@3.0.0: - resolution: {integrity: sha512-yp+e5N9V3C6bwBeAC4n796kc86M4gJCdlVhiMTxIrJG5UHDMh+PJANf9heqORJbt1nrCbDwIlAZKjANIaVBbvw==} - - rehype-slug@6.0.0: - resolution: {integrity: sha512-lWyvf/jwu+oS5+hL5eClVd3hNdmwM1kAC0BUvEGD19pajQMIzcNUd/k9GsfQ+FfECvX+JE+e9/btsKH0EjJT6A==} - remark-gfm@4.0.0: resolution: {integrity: sha512-U92vJgBPkbw4Zfu/IiW2oTZLSL3Zpv+uI7My2eq8JxKgqraFdU8YUGicEJCEgSbeaG+QDFqIcwwfMTOEelPxuA==} @@ -10013,18 +9662,16 @@ packages: resolution: {integrity: sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==} engines: {iojs: '>=1.0.0', node: '>=0.10.0'} - rfdc@1.3.0: - resolution: {integrity: sha512-V2hovdzFbOi77/WajaSMXk2OLm+xNIeQdMMuB7icj7bk6zi2F8GGAxigcnDFpJHbNyNcgyJDiP+8nOrY5cZGrA==} - rfdc@1.4.1: resolution: {integrity: sha512-q1b3N5QkRUWUl7iyylaaj3kOpIT0N2i9MqIEQXP73GVsN9cw3fdx8X63cEmWhJGi2PPCF23Ijp7ktmd39rawIA==} rimraf@3.0.2: resolution: {integrity: sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==} + deprecated: Rimraf versions prior to v4 are no longer supported hasBin: true - rollup@4.22.5: - resolution: {integrity: sha512-WoinX7GeQOFMGznEcWA1WrTQCd/tpEbMkc3nuMs9BT0CPjMdSjPMTVClwWd4pgSQwJdP65SK9mTCNvItlr5o7w==} + rollup@4.26.0: + resolution: {integrity: sha512-ilcl12hnWonG8f+NxU6BlgysVA0gvY2l8N0R84S1HcINbW20bvwuCngJkkInV6LXhwRpucsW5k1ovDwEdBVrNg==} engines: {node: '>=18.0.0', npm: '>=8.0.0'} hasBin: true @@ -10074,14 +9721,19 @@ packages: safer-buffer@2.1.2: resolution: {integrity: sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==} - sanitize-html@2.13.0: - resolution: {integrity: sha512-Xff91Z+4Mz5QiNSLdLWwjgBDm5b1RU6xBT0+12rapjiaR7SwfRdjw8f+6Rir2MXKLrDicRFHdb51hGOAxmsUIA==} + sanitize-html@2.13.1: + resolution: {integrity: sha512-ZXtKq89oue4RP7abL9wp/9URJcqQNABB5GGJ2acW1sdO8JTVl92f4ygD7Yc9Ze09VAZhnt2zegeU0tbNsdcLYg==} sass@1.79.3: resolution: {integrity: sha512-m7dZxh0W9EZ3cw50Me5GOuYm/tVAJAn91SUnohLRo9cXBixGUOdvmryN+dXpwR831bhoY3Zv7rEFt85PUwTmzA==} engines: {node: '>=14.0.0'} hasBin: true + sass@1.79.4: + resolution: {integrity: sha512-K0QDSNPXgyqO4GZq2HO5Q70TLxTH6cIT59RdoCHMivrC8rqzaTw5ab9prjz9KUN1El4FLXrBXJhik61JR4HcGg==} + engines: {node: '>=14.0.0'} + hasBin: true + sax@1.2.4: resolution: {integrity: sha512-NqVDv9TpANUjFm0N8uM5GxL36UgKi9/atZw+x7YFnQ8ckwFGKrl4xX4yWtrey3UJm5nP1kUbnYgLopqWNSRhWw==} @@ -10132,18 +9784,10 @@ packages: engines: {node: '>=10'} hasBin: true - send@0.18.0: - resolution: {integrity: sha512-qqWzuOjSFOuqPjFe4NOsMLafToQQwBSOEpS+FwEt3A2V3vKubTquT3vmLTQpFgMXp8AlFWFuP1qKaJZOtPpVXg==} - engines: {node: '>= 0.8.0'} - send@0.19.0: resolution: {integrity: sha512-dW41u5VfLXu8SJh5bwRmyYUbAoSB3c9uQh6L8h/KtsFREPWpbX1lrljJo186Jc4nmci/sGUZ9a0a0J2zgfq2hw==} engines: {node: '>= 0.8.0'} - serve-static@1.15.0: - resolution: {integrity: sha512-XGuRDNjXUijsUL0vl6nSD7cwURuzEgglbOaFuZM9g3kwDXOWVTck0jLzjPzGD+TazWbboZYu52/9/XPdUgne9g==} - engines: {node: '>= 0.8.0'} - serve-static@1.16.2: resolution: {integrity: sha512-VqpjJZKadQB/PEbEwvFdO43Ax5dFBZ2UECszz8bQ7pi7wt//PWe1P6MN7eCnjsatYtBT6EuiClbjSWP2WrIoTw==} engines: {node: '>= 0.8.0'} @@ -10195,8 +9839,8 @@ packages: shiki@0.14.7: resolution: {integrity: sha512-dNPAPrxSc87ua2sKJ3H5dQ/6ZaY8RNnaAqK+t0eG7p0Soi2ydiqbGOTaZCqaYvA/uZYfS1LJnemt3Q+mSfcPCg==} - shiki@1.12.0: - resolution: {integrity: sha512-BuAxWOm5JhRcbSOl7XCei8wGjgJJonnV0oipUupPY58iULxUGyHhW5CF+9FRMuM1pcJ5cGEJGll1LusX6FwpPA==} + shiki@1.22.2: + resolution: {integrity: sha512-3IZau0NdGKXhH2bBlUk4w1IHNxPh6A5B2sUpyY+8utLu2j/h1QpFkAaUA1bAMxOWWGtTWcAh531vnS4NJKS/lA==} shimmer@1.2.1: resolution: {integrity: sha512-sQTKC1Re/rM6XyFM6fIAGHRPVGvyXfgzIDvzoq608vM+jeyVD0Tu1E6Np0Kc2zAIFWIj963V2800iF/9LPieQw==} @@ -10465,9 +10109,14 @@ packages: react-dom: optional: true - storybook@8.3.3: - resolution: {integrity: sha512-FG2KAVQN54T9R6voudiEftehtkXtLO+YVGP2gBPfacEdDQjY++ld7kTbHzpTT/bpCDx7Yq3dqOegLm9arVJfYw==} + storybook@8.4.4: + resolution: {integrity: sha512-xBOq3q/MuUUg3zM0imMMaK5ziKq3TO388jsnaiemJ4Uf0ZGwcHjM8HDBCDt0s5/CfsOQ49zo1ouZ3aNlu0qsUg==} hasBin: true + peerDependencies: + prettier: ^2 || ^3 + peerDependenciesMeta: + prettier: + optional: true stream-browserify@3.0.0: resolution: {integrity: sha512-H73RAHsVBapbim0tU2JwwOiXUj+fikfiaoYAKHF3VJfA0pe2BCzkhAHBlLG6REzE+2WNZcxOXjK7lkso+9euLA==} @@ -10534,6 +10183,9 @@ packages: string_decoder@1.3.0: resolution: {integrity: sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==} + stringify-entities@4.0.4: + resolution: {integrity: sha512-IwfBptatlO+QCJUo19AqvrPNqlVMpW9YEL2LIVY+Rpv2qsjCGxaDLNRgeGsQWJhfItebuJhsGSLjaBbNSQ+ieg==} + stringz@2.1.0: resolution: {integrity: sha512-KlywLT+MZ+v0IRepfMxRtnSvDCMc3nR1qqCs3m/qIbSOWkNZYT8XHQA31rS3TnKp0c5xjZu3M4GY/2aRKSi/6A==} @@ -10565,10 +10217,6 @@ packages: resolution: {integrity: sha512-dOESqjYr96iWYylGObzd39EuNTa5VJxyvVAEm5Jnh7KGo75V43Hk1odPQkNDyXNmUR6k+gEiDVXnjB8HJ3crXw==} engines: {node: '>=12'} - strip-final-newline@4.0.0: - resolution: {integrity: sha512-aulFJcD6YK8V1G7iRB5tigAP4TsHBZZrOV8pjV++zdUwmeV8uzbY7yn6h9MswN62adStNZFuCIx4haBnRuMDaw==} - engines: {node: '>=18'} - strip-indent@3.0.0: resolution: {integrity: sha512-laJTa3Jb+VQpaC6DseHhF7dXVqHTfJPCRDaEbid/drOhgitgYku/letMUqOXFoWV0zIIUbjpdH2t+tYj4bQMRQ==} engines: {node: '>=8'} @@ -10595,8 +10243,8 @@ packages: resolution: {integrity: sha512-pQ+V+nYQdC5H3Q7qBZAz/MO6lwGhoC2gOAjuouGf/VO0m7vQRh8QNMl2Uf6SwAtzZ9bOw3UIeBukEGNJl5dtXQ==} engines: {node: '>=14.16'} - strtok3@8.1.0: - resolution: {integrity: sha512-ExzDvHYPj6F6QkSNe/JxSlBxTh3OrI6wrAIz53ulxo1c4hBJ1bT9C/JrAthEKHWG9riVH3Xzg7B03Oxty6S2Lw==} + strtok3@9.0.1: + resolution: {integrity: sha512-ERPW+XkvX9W2A+ov07iy+ZFJpVdik04GhDA4eVogiG9hpC97Kem2iucyzhFxbFRvQ5o2UckFtKZdp1hkGvnrEw==} engines: {node: '>=16'} stylehacks@6.1.1: @@ -10654,11 +10302,8 @@ packages: resolution: {integrity: sha512-+HRtZ40Vc+6YfCDWCeAsixwxJgMbPY4HHuTgzPYH3JXvqHWUlsCfy+ylXlAKhFNcuLp4xVeWeFBUhDk+7KYUvQ==} engines: {node: '>=14.16'} - telejson@7.2.0: - resolution: {integrity: sha512-1QTEcJkJEhc8OnStBx/ILRu5J2p0GjvWsBx56bmZRqnrkdBMUe+nX92jxV+p3dB4CP6PZCdJMQJwCggkNBMzkQ==} - - terser@5.33.0: - resolution: {integrity: sha512-JuPVaB7s1gdFKPKTelwUyRq5Sid2A3Gko2S0PncwdBq7kN9Ti9HPWDQ06MPsEDGsZeVESjKEnyGy68quBk1w6g==} + terser@5.36.0: + resolution: {integrity: sha512-IYV9eNMuFAV4THUspIRXkLakHnV6XO7FEdtKjf/mDyrnqUg9LnlOn6/RwRvM9SZjR4GUq8Nk8zj67FzVARr74w==} engines: {node: '>=10'} hasBin: true @@ -10723,6 +10368,13 @@ packages: resolution: {integrity: sha512-n1cw8k1k0x4pgA2+9XrOkFydTerNcJ1zWCO5Nn9scWHTD+5tp8dghT2x1uduQePZTZgd3Tupf+x9BxJjeJi77Q==} engines: {node: '>=14.0.0'} + tldts-core@6.1.63: + resolution: {integrity: sha512-H1XCt54xY+QPbwhTgmxLkepX0MVHu3USfMmejiCOdkMbRcP22Pn2FVF127r/GWXVDmXTRezyF3Ckvhn4Fs6j7Q==} + + tldts@6.1.63: + resolution: {integrity: sha512-YWwhsjyn9sB/1rOkSRYxvkN/wl5LFM1QDv6F2pVR+pb/jFne4EOBxHfkKVWvDIBEAw9iGOwwubHtQTm0WRT5sQ==} + hasBin: true + tmp@0.2.3: resolution: {integrity: sha512-nZD7m9iCPC5g0pYmcaxogYKggSfLsdxl8of3Q/oIbqCqLLIO9IAF0GWjX1z9NZRHPiXv8Wex4yDCaZsgEw0Y8w==} engines: {node: '>=14.14'} @@ -10768,6 +10420,10 @@ packages: resolution: {integrity: sha512-Loo5UUvLD9ScZ6jh8beX1T6sO1w2/MpCRpEP7V280GKMVUQ0Jzar2U3UJPsrdbziLEMMhu3Ujnq//rhiFuIeag==} engines: {node: '>=6'} + tough-cookie@5.0.0: + resolution: {integrity: sha512-FRKsF7cz96xIIeMZ82ehjC3xW2E+O2+v11udrDYewUbszngYhsGa8z6YUMMzO9QJZzzyd0nGGXnML/TReX6W8Q==} + engines: {node: '>=16'} + tr46@0.0.3: resolution: {integrity: sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==} @@ -10778,6 +10434,13 @@ packages: trace-redirect@1.0.6: resolution: {integrity: sha512-UUfa1DjjU5flcjMdaFIiIEGDTyu2y/IiMjOX4uGXa7meKBS4vD4f2Uy/tken9Qkd4Jsm4sRsfZcIIPqrRVF3Mg==} + tree-kill@1.2.2: + resolution: {integrity: sha512-L0Orpi8qGpRG//Nd+H90vFB+3iHnue1zSSGmNOOCh1GLJ7rUKVwV2HvijphGQS2UmhUZewS9VgvxYIdgr+fG1A==} + hasBin: true + + trim-lines@3.0.1: + resolution: {integrity: sha512-kRj8B+YHZCc9kQYdWfJB2/oUl9rA99qbowYYBtr4ui4mZyAQ2JpvVBd/6U2YloATfqBhBTSMhTpgBHtU0Mf3Rg==} + trim-newlines@3.0.1: resolution: {integrity: sha512-c1PTsA3tYrIsLGkJkzHF+w9F2EyxfXGo4UyJc4pFL++FMjnq0HJS69T3M7d//gKrFKwy429bouPescbjecU+Zw==} engines: {node: '>=8'} @@ -10795,8 +10458,8 @@ packages: peerDependencies: typescript: '>=4.2.0' - ts-case-convert@2.0.7: - resolution: {integrity: sha512-Kqj8wrkuduWsKUOUNRczrkdHCDt4ZNNd6HKjVw42EnMIGHQUABS4pqfy0acETVLwUTppc1fzo/yi11+uMTaqzw==} + ts-case-convert@2.1.0: + resolution: {integrity: sha512-Ye79el/pHYXfoew6kqhMwCoxp4NWjKNcm2kBzpmEMIU9dd9aBmHNNFtZ+WTm0rz1ngyDmfqDXDlyUnBXayiD0w==} ts-dedent@2.2.0: resolution: {integrity: sha512-q5W7tVM71e2xjHZTlgfTDoPF/SmqKG5hddq9SzR49CH2hayqRKJtQ4mtRlSxKaJlR/+9rEM+mnBHf7I2/BQcpQ==} @@ -10894,8 +10557,8 @@ packages: resolution: {integrity: sha512-RAH822pAdBgcNMAfWnCBU3CFZcfZ/i1eZjwFU/dsLKumyuuP3niueg2UAukXYF0E2AAoc82ZSSf9J0WQBinzHA==} engines: {node: '>=12.20'} - type-fest@4.20.1: - resolution: {integrity: sha512-R6wDsVsoS9xYOpy8vgeBlqpdOyzJ12HNfQhC/aAKWM3YoCV9TtunJzh/QpkMgeDhkoynDcw5f1y+qF9yc/HHyg==} + type-fest@4.27.0: + resolution: {integrity: sha512-3IMSWgP7C5KSQqmo1wjhKrwsvXAtF33jO3QY+Uy++ia7hqvgSK6iXbbg5PbDBc1P2ZbNEDgejOrN4YooXvhwCw==} engines: {node: '>=16'} type-is@1.6.18: @@ -11006,23 +10669,13 @@ packages: engines: {node: '>=14.17'} hasBin: true - typescript@5.3.3: - resolution: {integrity: sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==} - engines: {node: '>=14.17'} - hasBin: true - typescript@5.4.2: resolution: {integrity: sha512-+2/g0Fds1ERlP6JsakQQDXjZdZMM+rqpamFZJEKh4kwTIn3iDkgKtby0CeNd5ATNZ4Ry1ax15TMx0W2V+miizQ==} engines: {node: '>=14.17'} hasBin: true - typescript@5.5.4: - resolution: {integrity: sha512-Mtq29sKDAEYP7aljRgtPOpTvOfbwRWlS6dPRzwjdE+C0R4brX/GUyhHSecbHMFLNBLcJIPt9nl9yG5TZ1weH+Q==} - engines: {node: '>=14.17'} - hasBin: true - - typescript@5.6.2: - resolution: {integrity: sha512-NW8ByodCSNCwZeghjN3o+JX5OFH0Ojg6sadjEKY4huZ52TqbJTJnDo5+Tw98lSy63NZvi4n+ez5m2u5d4PkZyw==} + typescript@5.6.3: + resolution: {integrity: sha512-hjcS1mhfuyi4WW8IWtjP7brDrG2cuDZukyrYrSauoXGNgx0S7zceP07adYkJycEr56BOUTNPzbInooiN3fn1qw==} engines: {node: '>=14.17'} hasBin: true @@ -11050,9 +10703,6 @@ packages: undefsafe@2.0.5: resolution: {integrity: sha512-WxONCrssBM8TSPRqN5EmsjVrsv4A8X12J4ArBiiayv3DyyG3ZlIg6yysuuSYdZsVz3TKcTg2fd//Ujd4CHV1iA==} - undici-types@5.26.5: - resolution: {integrity: sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==} - undici-types@6.19.8: resolution: {integrity: sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==} @@ -11064,10 +10714,6 @@ packages: resolution: {integrity: sha512-AITZfPuxubm31Sx0vr8bteSalEbs9wQb/BOBi9FPlD9Qpd6HxZ4Q0+hI742jBhkPb4RT2v5MQzaW5VhRVyj+9A==} engines: {node: '>=18.17'} - unicorn-magic@0.3.0: - resolution: {integrity: sha512-+QBBXBCvifc56fsbuxZQ6Sic3wqqc3WWaqxs58gvJrcOuN83HGTCwz3oS5phzU9LthRNE9VrJCFCLUgHeeFnfA==} - engines: {node: '>=18'} - unified@11.0.4: resolution: {integrity: sha512-apMPnyLjAX+ty4OrNap7yumyVAMlKx5IWU2wlzzUdYJO9A8f1p9m/gywF/GM2ZDFcjQPrx59Mc90KwmxsoklxQ==} @@ -11082,6 +10728,9 @@ packages: unist-util-is@6.0.0: resolution: {integrity: sha512-2qCTHimwdxLfz+YzdGfkqNlH0tLi9xjTnHddPmJwtIG9MGsdbutfTc4P+haPD7l7Cjxf/WZj+we5qfVPvvxfYw==} + unist-util-position@5.0.0: + resolution: {integrity: sha512-fucsC7HjXvkB5R3kTCO7kUjRdrS0BJt3M/FPxmHMBOm8JQi2BsHAHFsy27E0EolP8rp0NzXsJ+jNPyDWvOJZPA==} + unist-util-stringify-position@4.0.0: resolution: {integrity: sha512-0ASV06AAoKCDkS2+xw5RXJywruurpbC4JZSm7nr7MOt1ojAzvyyaO+UxZf18j8FCF6kmzCZKcAgN/yu2gm2XgQ==} @@ -11201,8 +10850,8 @@ packages: vite-plugin-turbosnap@1.0.3: resolution: {integrity: sha512-p4D8CFVhZS412SyQX125qxyzOgIFouwOcvjZWk6bQbNPR1wtaEzFT6jZxAjf1dejlGqa6fqHcuCvQea6EWUkUA==} - vite@5.4.8: - resolution: {integrity: sha512-FqrItQ4DT1NC4zCUqMB4c4AZORMKIa0m8/URVCZ77OZ/QSNeJ54bU1vrFADbDsuwfIPcgknRkmqakQcgnL4GiQ==} + vite@5.4.11: + resolution: {integrity: sha512-c7jFQRklXua0mTzneGW9QVyxFjUgwcihC4bXEtujIo2ouWCe1Ajt/amn2PCxYnhYfd5k09JX3SB7OYWFKYqj8Q==} engines: {node: ^18.0.0 || >=20.0.0} hasBin: true peerDependencies: @@ -11314,9 +10963,6 @@ packages: vue-component-type-helpers@2.1.10: resolution: {integrity: sha512-lfgdSLQKrUmADiSV6PbBvYgQ33KF3Ztv6gP85MfGaGaSGMTXORVaHT1EHfsqCgzRNBstPKYDmvAV9Do5CmJ07A==} - vue-component-type-helpers@2.1.6: - resolution: {integrity: sha512-ng11B8B/ZADUMMOsRbqv0arc442q7lifSubD0v8oDXIFoMg/mXwAPUunrroIDkY+mcD0dHKccdaznSVp8EoX3w==} - vue-demi@0.14.7: resolution: {integrity: sha512-EOG8KXDQNwkJILkx/gPcoL/7vH+hORoBaKgGe+6W7VFMvCYJfmF2dGbvgDroVnI8LU7/kTu8mbjRZGBU1z9NTA==} engines: {node: '>=12'} @@ -11347,22 +10993,14 @@ packages: vue-template-compiler@2.7.14: resolution: {integrity: sha512-zyA5Y3ArvVG0NacJDkkzJuPQDF8RFeRlzV2vLeSnhSpieO6LK2OVbdLPi5MPPs09Ii+gMO8nY4S3iKQxBxDmWQ==} - vue-tsc@2.1.6: - resolution: {integrity: sha512-f98dyZp5FOukcYmbFpuSCJ4Z0vHSOSmxGttZJCsFeX0M4w/Rsq0s4uKXjcSRsZqsRgQa6z7SfuO+y0HVICE57Q==} + vue-tsc@2.1.10: + resolution: {integrity: sha512-RBNSfaaRHcN5uqVqJSZh++Gy/YUzryuv9u1aFWhsammDJXNtUiJMNoJ747lZcQ68wUQFx6E73y4FY3D8E7FGMA==} hasBin: true peerDependencies: typescript: '>=5.0.0' - vue@3.4.37: - resolution: {integrity: sha512-3vXvNfkKTBsSJ7JP+LyR7GBuwQuckbWvuwAid3xbqK9ppsKt/DUvfqgZ48fgOLEfpy1IacL5f8QhUVl77RaI7A==} - peerDependencies: - typescript: '*' - peerDependenciesMeta: - typescript: - optional: true - - vue@3.5.10: - resolution: {integrity: sha512-Vy2kmJwHPlouC/tSnIgXVg03SG+9wSqT1xu1Vehc+ChsXsRd7jLkKgMltVEFOzUdBr3uFwBCG+41LJtfAcBRng==} + vue@3.5.12: + resolution: {integrity: sha512-CLVZtXtn2ItBIi/zHZ0Sg1Xkb7+PU32bJJ8Bmy7ts3jxXTcbfsEfBivFYYWz1Hur+lalqGAh65Coin0r+HRUfg==} peerDependencies: typescript: '*' peerDependenciesMeta: @@ -11608,8 +11246,8 @@ packages: resolution: {integrity: sha512-9bnSc/HEW2uRy67wc+T8UwauLuPJVn28jb+GtJY16iiKWyvmYJRXVT4UamsAEGQfPohgr2q4Tq0sQbQlxTfi1g==} engines: {node: '>=12.20'} - yoctocolors@2.0.2: - resolution: {integrity: sha512-Ct97huExsu7cWeEjmrXlofevF8CvzUglJ4iGUet5B8xn1oumtAZBpHU4GzYuoE6PVqcZ5hghtBrSlhwHuR1Jmw==} + yoctocolors-cjs@2.1.2: + resolution: {integrity: sha512-cYVsTjKl8b+FrnidjibDWskAv7UKOfcwaVZdp/it9n1s9fU3IkgDbhdIRKCW4JDsAlECJY0ytoVPT3sK6kideA==} engines: {node: '>=18'} zip-stream@6.0.1: @@ -11642,13 +11280,13 @@ snapshots: dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.609.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/crc32c@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.609.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/sha1-browser@5.2.0': dependencies: @@ -11657,7 +11295,7 @@ snapshots: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.208.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/sha256-browser@5.2.0': dependencies: @@ -11667,23 +11305,23 @@ snapshots: '@aws-sdk/types': 3.609.0 '@aws-sdk/util-locate-window': 3.208.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/sha256-js@5.2.0': dependencies: '@aws-crypto/util': 5.2.0 '@aws-sdk/types': 3.609.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/supports-web-crypto@5.2.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@aws-crypto/util@5.2.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/util-utf8': 2.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/client-s3@3.620.0': dependencies: @@ -11789,7 +11427,7 @@ snapshots: '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - aws-crt @@ -11832,7 +11470,7 @@ snapshots: '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - aws-crt @@ -11877,7 +11515,7 @@ snapshots: '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - aws-crt @@ -11889,14 +11527,14 @@ snapshots: '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 fast-xml-parser: 4.2.5 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/credential-provider-env@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/credential-provider-http@3.620.0': dependencies: @@ -11908,7 +11546,7 @@ snapshots: '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 '@smithy/util-stream': 3.1.3 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/credential-provider-ini@3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))(@aws-sdk/client-sts@3.620.0)': dependencies: @@ -11923,7 +11561,7 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11941,7 +11579,7 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - '@aws-sdk/client-sts' @@ -11953,7 +11591,7 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/credential-provider-sso@3.620.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))': dependencies: @@ -11963,7 +11601,7 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 transitivePeerDependencies: - '@aws-sdk/client-sso-oidc' - aws-crt @@ -11974,7 +11612,7 @@ snapshots: '@aws-sdk/types': 3.609.0 '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/lib-storage@3.620.0(@aws-sdk/client-s3@3.620.0)': dependencies: @@ -11995,14 +11633,14 @@ snapshots: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-expect-continue@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-flexible-checksums@3.620.0': dependencies: @@ -12013,33 +11651,33 @@ snapshots: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-host-header@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-location-constraint@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-logger@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-recursion-detection@3.620.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-sdk-s3@3.620.0': dependencies: @@ -12053,7 +11691,7 @@ snapshots: '@smithy/util-config-provider': 3.0.0 '@smithy/util-stream': 3.1.3 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-signing@3.620.0': dependencies: @@ -12063,13 +11701,13 @@ snapshots: '@smithy/signature-v4': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-ssec@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/middleware-user-agent@3.620.0': dependencies: @@ -12077,7 +11715,7 @@ snapshots: '@aws-sdk/util-endpoints': 3.614.0 '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/region-config-resolver@3.614.0': dependencies: @@ -12086,7 +11724,7 @@ snapshots: '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/signature-v4-multi-region@3.620.0': dependencies: @@ -12095,7 +11733,7 @@ snapshots: '@smithy/protocol-http': 4.1.0 '@smithy/signature-v4': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/token-providers@3.614.0(@aws-sdk/client-sso-oidc@3.620.0(@aws-sdk/client-sts@3.620.0))': dependencies: @@ -12104,54 +11742,81 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/types@3.609.0': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/util-arn-parser@3.568.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/util-endpoints@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 '@smithy/util-endpoints': 2.0.5 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/util-locate-window@3.208.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/util-user-agent-browser@3.609.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/types': 3.3.0 bowser: 2.11.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/util-user-agent-node@3.614.0': dependencies: '@aws-sdk/types': 3.609.0 '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@aws-sdk/xml-builder@3.609.0': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 + + '@babel/code-frame@7.23.5': + dependencies: + '@babel/highlight': 7.24.7 + chalk: 2.4.2 '@babel/code-frame@7.24.7': dependencies: '@babel/highlight': 7.24.7 picocolors: 1.0.1 + '@babel/compat-data@7.23.5': {} + '@babel/compat-data@7.24.7': {} + '@babel/core@7.23.5': + dependencies: + '@ampproject/remapping': 2.2.1 + '@babel/code-frame': 7.23.5 + '@babel/generator': 7.24.7 + '@babel/helper-compilation-targets': 7.22.15 + '@babel/helper-module-transforms': 7.23.3(@babel/core@7.23.5) + '@babel/helpers': 7.23.5 + '@babel/parser': 7.24.7 + '@babel/template': 7.22.15 + '@babel/traverse': 7.23.5 + '@babel/types': 7.24.7 + convert-source-map: 2.0.0 + debug: 4.3.5(supports-color@5.5.0) + gensync: 1.0.0-beta.2 + json5: 2.2.3 + semver: 6.3.1 + transitivePeerDependencies: + - supports-color + '@babel/core@7.24.7': dependencies: '@ampproject/remapping': 2.2.1 @@ -12160,12 +11825,12 @@ snapshots: '@babel/helper-compilation-targets': 7.24.7 '@babel/helper-module-transforms': 7.24.7(@babel/core@7.24.7) '@babel/helpers': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.7 '@babel/template': 7.24.7 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 convert-source-map: 2.0.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) gensync: 1.0.0-beta.2 json5: 2.2.3 semver: 6.3.1 @@ -12179,6 +11844,14 @@ snapshots: '@jridgewell/trace-mapping': 0.3.25 jsesc: 2.5.2 + '@babel/helper-compilation-targets@7.22.15': + dependencies: + '@babel/compat-data': 7.23.5 + '@babel/helper-validator-option': 7.23.5 + browserslist: 4.22.2 + lru-cache: 5.1.1 + semver: 6.3.1 + '@babel/helper-compilation-targets@7.24.7': dependencies: '@babel/compat-data': 7.24.7 @@ -12194,19 +11867,32 @@ snapshots: '@babel/helper-function-name@7.24.7': dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 '@babel/helper-hoist-variables@7.24.7': dependencies: + '@babel/types': 7.25.7 + + '@babel/helper-module-imports@7.22.15': + dependencies: '@babel/types': 7.24.7 '@babel/helper-module-imports@7.24.7': dependencies: '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 transitivePeerDependencies: - supports-color + '@babel/helper-module-transforms@7.23.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-module-imports': 7.22.15 + '@babel/helper-simple-access': 7.22.5 + '@babel/helper-split-export-declaration': 7.24.7 + '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-module-transforms@7.24.7(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 @@ -12214,18 +11900,20 @@ snapshots: '@babel/helper-module-imports': 7.24.7 '@babel/helper-simple-access': 7.24.7 '@babel/helper-split-export-declaration': 7.24.7 - '@babel/helper-validator-identifier': 7.24.7 + '@babel/helper-validator-identifier': 7.25.7 transitivePeerDependencies: - supports-color '@babel/helper-plugin-utils@7.22.5': {} - '@babel/helper-plugin-utils@7.24.7': {} + '@babel/helper-simple-access@7.22.5': + dependencies: + '@babel/types': 7.24.7 '@babel/helper-simple-access@7.24.7': dependencies: '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 transitivePeerDependencies: - supports-color @@ -12241,12 +11929,22 @@ snapshots: '@babel/helper-validator-identifier@7.25.7': {} + '@babel/helper-validator-option@7.23.5': {} + '@babel/helper-validator-option@7.24.7': {} + '@babel/helpers@7.23.5': + dependencies: + '@babel/template': 7.22.15 + '@babel/traverse': 7.24.7 + '@babel/types': 7.24.7 + transitivePeerDependencies: + - supports-color + '@babel/helpers@7.24.7': dependencies: '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 '@babel/highlight@7.24.7': dependencies: @@ -12263,85 +11961,184 @@ snapshots: dependencies: '@babel/types': 7.25.7 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 + '@babel/plugin-syntax-async-generators@7.8.4(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-bigint@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-class-properties@7.12.13(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-import-meta@7.10.4(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-json-strings@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true - '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.24.7)': + '@babel/plugin-syntax-jsx@7.23.3(@babel/core@7.23.5)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 + + '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-logical-assignment-operators@7.10.4(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-nullish-coalescing-operator@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-numeric-separator@7.10.4(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-object-rest-spread@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-optional-catch-binding@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-optional-chaining@7.8.3(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true + + '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.23.5)': + dependencies: + '@babel/core': 7.23.5 + '@babel/helper-plugin-utils': 7.22.5 '@babel/plugin-syntax-top-level-await@7.14.5(@babel/core@7.24.7)': dependencies: '@babel/core': 7.24.7 '@babel/helper-plugin-utils': 7.22.5 + optional: true - '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.24.7)': + '@babel/plugin-syntax-typescript@7.23.3(@babel/core@7.23.5)': dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.23.5 '@babel/helper-plugin-utils': 7.22.5 '@babel/runtime@7.23.4': dependencies: regenerator-runtime: 0.14.0 + '@babel/template@7.22.15': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.24.7 + '@babel/types': 7.24.7 + + '@babel/template@7.24.0': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + '@babel/template@7.24.7': dependencies: '@babel/code-frame': 7.24.7 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + + '@babel/traverse@7.23.5': + dependencies: + '@babel/code-frame': 7.24.7 + '@babel/generator': 7.24.7 + '@babel/helper-environment-visitor': 7.24.7 + '@babel/helper-function-name': 7.24.7 + '@babel/helper-hoist-variables': 7.24.7 + '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 + debug: 4.3.5(supports-color@5.5.0) + globals: 11.12.0 + transitivePeerDependencies: + - supports-color '@babel/traverse@7.24.7': dependencies: @@ -12353,7 +12150,7 @@ snapshots: '@babel/helper-split-export-declaration': 7.24.7 '@babel/parser': 7.24.7 '@babel/types': 7.24.7 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) globals: 11.12.0 transitivePeerDependencies: - supports-color @@ -12370,30 +12167,28 @@ snapshots: '@babel/helper-validator-identifier': 7.25.7 to-fast-properties: 2.0.0 - '@base2/pretty-print-object@1.0.1': {} - '@bcoe/v8-coverage@0.2.3': {} - '@bull-board/api@6.0.0(@bull-board/ui@6.0.0)': + '@bull-board/api@6.5.0(@bull-board/ui@6.5.0)': dependencies: - '@bull-board/ui': 6.0.0 + '@bull-board/ui': 6.5.0 redis-info: 3.1.0 - '@bull-board/fastify@6.0.0': + '@bull-board/fastify@6.5.0': dependencies: - '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0) - '@bull-board/ui': 6.0.0 - '@fastify/static': 8.0.1 + '@bull-board/api': 6.5.0(@bull-board/ui@6.5.0) + '@bull-board/ui': 6.5.0 + '@fastify/static': 8.0.2 '@fastify/view': 10.0.1 ejs: 3.1.10 - '@bull-board/ui@6.0.0': + '@bull-board/ui@6.5.0': dependencies: - '@bull-board/api': 6.0.0(@bull-board/ui@6.0.0) + '@bull-board/api': 6.5.0(@bull-board/ui@6.5.0) - '@bundled-es-modules/cookie@2.0.0': + '@bundled-es-modules/cookie@2.0.1': dependencies: - cookie: 0.5.0 + cookie: 0.7.2 '@bundled-es-modules/statuses@1.0.1': dependencies: @@ -12475,7 +12270,7 @@ snapshots: '@cropper/utils@2.0.0-rc.2': {} - '@cypress/request@3.0.5': + '@cypress/request@3.0.6': dependencies: aws-sign2: 0.7.0 aws4: 1.12.0 @@ -12483,7 +12278,7 @@ snapshots: combined-stream: 1.0.8 extend: 3.0.2 forever-agent: 0.6.1 - form-data: 4.0.0 + form-data: 4.0.1 http-signature: 1.4.0 is-typedarray: 1.0.0 isstream: 0.1.2 @@ -12492,7 +12287,7 @@ snapshots: performance-now: 2.1.0 qs: 6.13.0 safe-buffer: 5.2.1 - tough-cookie: 4.1.4 + tough-cookie: 5.0.0 tunnel-agent: 0.6.0 uuid: 8.3.2 optional: true @@ -12522,355 +12317,214 @@ snapshots: '@emnapi/runtime@1.3.0': dependencies: - tslib: 2.6.3 - optional: true - - '@esbuild/aix-ppc64@0.19.11': + tslib: 2.7.0 optional: true '@esbuild/aix-ppc64@0.21.5': optional: true - '@esbuild/aix-ppc64@0.23.0': - optional: true - - '@esbuild/aix-ppc64@0.23.1': + '@esbuild/aix-ppc64@0.24.0': optional: true '@esbuild/android-arm64@0.18.20': optional: true - '@esbuild/android-arm64@0.19.11': - optional: true - '@esbuild/android-arm64@0.21.5': optional: true - '@esbuild/android-arm64@0.23.0': - optional: true - - '@esbuild/android-arm64@0.23.1': + '@esbuild/android-arm64@0.24.0': optional: true '@esbuild/android-arm@0.18.20': optional: true - '@esbuild/android-arm@0.19.11': - optional: true - '@esbuild/android-arm@0.21.5': optional: true - '@esbuild/android-arm@0.23.0': - optional: true - - '@esbuild/android-arm@0.23.1': + '@esbuild/android-arm@0.24.0': optional: true '@esbuild/android-x64@0.18.20': optional: true - '@esbuild/android-x64@0.19.11': - optional: true - '@esbuild/android-x64@0.21.5': optional: true - '@esbuild/android-x64@0.23.0': - optional: true - - '@esbuild/android-x64@0.23.1': + '@esbuild/android-x64@0.24.0': optional: true '@esbuild/darwin-arm64@0.18.20': optional: true - '@esbuild/darwin-arm64@0.19.11': - optional: true - '@esbuild/darwin-arm64@0.21.5': optional: true - '@esbuild/darwin-arm64@0.23.0': - optional: true - - '@esbuild/darwin-arm64@0.23.1': + '@esbuild/darwin-arm64@0.24.0': optional: true '@esbuild/darwin-x64@0.18.20': optional: true - '@esbuild/darwin-x64@0.19.11': - optional: true - '@esbuild/darwin-x64@0.21.5': optional: true - '@esbuild/darwin-x64@0.23.0': - optional: true - - '@esbuild/darwin-x64@0.23.1': + '@esbuild/darwin-x64@0.24.0': optional: true '@esbuild/freebsd-arm64@0.18.20': optional: true - '@esbuild/freebsd-arm64@0.19.11': - optional: true - '@esbuild/freebsd-arm64@0.21.5': optional: true - '@esbuild/freebsd-arm64@0.23.0': - optional: true - - '@esbuild/freebsd-arm64@0.23.1': + '@esbuild/freebsd-arm64@0.24.0': optional: true '@esbuild/freebsd-x64@0.18.20': optional: true - '@esbuild/freebsd-x64@0.19.11': - optional: true - '@esbuild/freebsd-x64@0.21.5': optional: true - '@esbuild/freebsd-x64@0.23.0': - optional: true - - '@esbuild/freebsd-x64@0.23.1': + '@esbuild/freebsd-x64@0.24.0': optional: true '@esbuild/linux-arm64@0.18.20': optional: true - '@esbuild/linux-arm64@0.19.11': - optional: true - '@esbuild/linux-arm64@0.21.5': optional: true - '@esbuild/linux-arm64@0.23.0': - optional: true - - '@esbuild/linux-arm64@0.23.1': + '@esbuild/linux-arm64@0.24.0': optional: true '@esbuild/linux-arm@0.18.20': optional: true - '@esbuild/linux-arm@0.19.11': - optional: true - '@esbuild/linux-arm@0.21.5': optional: true - '@esbuild/linux-arm@0.23.0': - optional: true - - '@esbuild/linux-arm@0.23.1': + '@esbuild/linux-arm@0.24.0': optional: true '@esbuild/linux-ia32@0.18.20': optional: true - '@esbuild/linux-ia32@0.19.11': - optional: true - '@esbuild/linux-ia32@0.21.5': optional: true - '@esbuild/linux-ia32@0.23.0': - optional: true - - '@esbuild/linux-ia32@0.23.1': + '@esbuild/linux-ia32@0.24.0': optional: true '@esbuild/linux-loong64@0.18.20': optional: true - '@esbuild/linux-loong64@0.19.11': - optional: true - '@esbuild/linux-loong64@0.21.5': optional: true - '@esbuild/linux-loong64@0.23.0': - optional: true - - '@esbuild/linux-loong64@0.23.1': + '@esbuild/linux-loong64@0.24.0': optional: true '@esbuild/linux-mips64el@0.18.20': optional: true - '@esbuild/linux-mips64el@0.19.11': - optional: true - '@esbuild/linux-mips64el@0.21.5': optional: true - '@esbuild/linux-mips64el@0.23.0': - optional: true - - '@esbuild/linux-mips64el@0.23.1': + '@esbuild/linux-mips64el@0.24.0': optional: true '@esbuild/linux-ppc64@0.18.20': optional: true - '@esbuild/linux-ppc64@0.19.11': - optional: true - '@esbuild/linux-ppc64@0.21.5': optional: true - '@esbuild/linux-ppc64@0.23.0': - optional: true - - '@esbuild/linux-ppc64@0.23.1': + '@esbuild/linux-ppc64@0.24.0': optional: true '@esbuild/linux-riscv64@0.18.20': optional: true - '@esbuild/linux-riscv64@0.19.11': - optional: true - '@esbuild/linux-riscv64@0.21.5': optional: true - '@esbuild/linux-riscv64@0.23.0': - optional: true - - '@esbuild/linux-riscv64@0.23.1': + '@esbuild/linux-riscv64@0.24.0': optional: true '@esbuild/linux-s390x@0.18.20': optional: true - '@esbuild/linux-s390x@0.19.11': - optional: true - '@esbuild/linux-s390x@0.21.5': optional: true - '@esbuild/linux-s390x@0.23.0': - optional: true - - '@esbuild/linux-s390x@0.23.1': + '@esbuild/linux-s390x@0.24.0': optional: true '@esbuild/linux-x64@0.18.20': optional: true - '@esbuild/linux-x64@0.19.11': - optional: true - '@esbuild/linux-x64@0.21.5': optional: true - '@esbuild/linux-x64@0.23.0': - optional: true - - '@esbuild/linux-x64@0.23.1': + '@esbuild/linux-x64@0.24.0': optional: true '@esbuild/netbsd-x64@0.18.20': optional: true - '@esbuild/netbsd-x64@0.19.11': - optional: true - '@esbuild/netbsd-x64@0.21.5': optional: true - '@esbuild/netbsd-x64@0.23.0': - optional: true - - '@esbuild/netbsd-x64@0.23.1': + '@esbuild/netbsd-x64@0.24.0': optional: true - '@esbuild/openbsd-arm64@0.23.0': - optional: true - - '@esbuild/openbsd-arm64@0.23.1': + '@esbuild/openbsd-arm64@0.24.0': optional: true '@esbuild/openbsd-x64@0.18.20': optional: true - '@esbuild/openbsd-x64@0.19.11': - optional: true - '@esbuild/openbsd-x64@0.21.5': optional: true - '@esbuild/openbsd-x64@0.23.0': - optional: true - - '@esbuild/openbsd-x64@0.23.1': + '@esbuild/openbsd-x64@0.24.0': optional: true '@esbuild/sunos-x64@0.18.20': optional: true - '@esbuild/sunos-x64@0.19.11': - optional: true - '@esbuild/sunos-x64@0.21.5': optional: true - '@esbuild/sunos-x64@0.23.0': - optional: true - - '@esbuild/sunos-x64@0.23.1': + '@esbuild/sunos-x64@0.24.0': optional: true '@esbuild/win32-arm64@0.18.20': optional: true - '@esbuild/win32-arm64@0.19.11': - optional: true - '@esbuild/win32-arm64@0.21.5': optional: true - '@esbuild/win32-arm64@0.23.0': - optional: true - - '@esbuild/win32-arm64@0.23.1': + '@esbuild/win32-arm64@0.24.0': optional: true '@esbuild/win32-ia32@0.18.20': optional: true - '@esbuild/win32-ia32@0.19.11': - optional: true - '@esbuild/win32-ia32@0.21.5': optional: true - '@esbuild/win32-ia32@0.23.0': - optional: true - - '@esbuild/win32-ia32@0.23.1': + '@esbuild/win32-ia32@0.24.0': optional: true '@esbuild/win32-x64@0.18.20': optional: true - '@esbuild/win32-x64@0.19.11': - optional: true - '@esbuild/win32-x64@0.21.5': optional: true - '@esbuild/win32-x64@0.23.0': - optional: true - - '@esbuild/win32-x64@0.23.1': + '@esbuild/win32-x64@0.24.0': optional: true '@eslint-community/eslint-utils@4.4.0(eslint@8.57.0)': @@ -12878,29 +12532,33 @@ snapshots: eslint: 8.57.0 eslint-visitor-keys: 3.4.3 - '@eslint-community/eslint-utils@4.4.0(eslint@9.8.0)': + '@eslint-community/eslint-utils@4.4.0(eslint@9.14.0)': dependencies: - eslint: 9.8.0 + eslint: 9.14.0 eslint-visitor-keys: 3.4.3 '@eslint-community/regexpp@4.11.0': {} + '@eslint-community/regexpp@4.12.1': {} + '@eslint-community/regexpp@4.6.2': {} '@eslint/compat@1.1.1': {} - '@eslint/config-array@0.17.1': + '@eslint/config-array@0.18.0': dependencies: '@eslint/object-schema': 2.1.4 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) minimatch: 3.1.2 transitivePeerDependencies: - supports-color + '@eslint/core@0.7.0': {} + '@eslint/eslintrc@2.1.4': dependencies: ajv: 6.12.6 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) espree: 9.6.1 globals: 13.24.0 ignore: 5.3.1 @@ -12914,8 +12572,8 @@ snapshots: '@eslint/eslintrc@3.1.0': dependencies: ajv: 6.12.6 - debug: 4.3.7 - espree: 10.1.0 + debug: 4.3.7(supports-color@8.1.1) + espree: 10.3.0 globals: 14.0.0 ignore: 5.3.1 import-fresh: 3.3.0 @@ -12927,13 +12585,17 @@ snapshots: '@eslint/js@8.57.0': {} - '@eslint/js@9.8.0': {} + '@eslint/js@9.14.0': {} '@eslint/object-schema@2.1.4': {} + '@eslint/plugin-kit@0.2.3': + dependencies: + levn: 0.4.1 + '@fastify/accept-negotiator@2.0.0': {} - '@fastify/accepts@5.0.0': + '@fastify/accepts@5.0.1': dependencies: accepts: 1.3.8 fastify-plugin: 5.0.1 @@ -12952,12 +12614,12 @@ snapshots: '@fastify/busboy@3.0.0': {} - '@fastify/cookie@10.0.0': + '@fastify/cookie@11.0.1': dependencies: - cookie-signature: 1.2.1 + cookie: 1.0.2 fastify-plugin: 5.0.1 - '@fastify/cors@10.0.0': + '@fastify/cors@10.0.1': dependencies: fastify-plugin: 5.0.1 mnemonist: 0.39.8 @@ -12966,9 +12628,9 @@ snapshots: '@fastify/error@4.0.0': {} - '@fastify/express@4.0.0': + '@fastify/express@4.0.1': dependencies: - express: 4.19.2 + express: 4.21.0 fastify-plugin: 5.0.1 transitivePeerDependencies: - supports-color @@ -12977,7 +12639,7 @@ snapshots: dependencies: fast-json-stringify: 6.0.0 - '@fastify/http-proxy@10.0.0(bufferutil@4.0.7)(utf-8-validate@6.0.3)': + '@fastify/http-proxy@10.0.1(bufferutil@4.0.7)(utf-8-validate@6.0.3)': dependencies: '@fastify/reply-from': 11.0.1 fast-querystring: 1.1.2 @@ -12991,7 +12653,7 @@ snapshots: dependencies: fast-deep-equal: 3.1.3 - '@fastify/multipart@9.0.0': + '@fastify/multipart@9.0.1': dependencies: '@fastify/busboy': 3.0.0 '@fastify/deepmerge': 2.0.0 @@ -13017,16 +12679,7 @@ snapshots: http-errors: 2.0.0 mime: 3.0.0 - '@fastify/static@8.0.0': - dependencies: - '@fastify/accept-negotiator': 2.0.0 - '@fastify/send': 3.1.1 - content-disposition: 0.5.4 - fastify-plugin: 5.0.1 - fastq: 1.17.1 - glob: 11.0.0 - - '@fastify/static@8.0.1': + '@fastify/static@8.0.2': dependencies: '@fastify/accept-negotiator': 2.0.0 '@fastify/send': 3.1.1 @@ -13035,11 +12688,6 @@ snapshots: fastq: 1.17.1 glob: 11.0.0 - '@fastify/view@10.0.0': - dependencies: - fastify-plugin: 5.0.1 - toad-cache: 3.7.0 - '@fastify/view@10.0.1': dependencies: fastify-plugin: 5.0.1 @@ -13069,10 +12717,17 @@ snapshots: '@hexagon/base64@1.1.27': {} + '@humanfs/core@0.19.1': {} + + '@humanfs/node@0.16.6': + dependencies: + '@humanfs/core': 0.19.1 + '@humanwhocodes/retry': 0.3.0 + '@humanwhocodes/config-array@0.11.14': dependencies: '@humanwhocodes/object-schema': 2.0.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) minimatch: 3.1.2 transitivePeerDependencies: - supports-color @@ -13085,6 +12740,8 @@ snapshots: '@humanwhocodes/retry@0.3.0': {} + '@humanwhocodes/retry@0.4.1': {} + '@img/sharp-darwin-arm64@0.33.5': optionalDependencies: '@img/sharp-libvips-darwin-arm64': 1.0.4 @@ -13160,30 +12817,31 @@ snapshots: '@img/sharp-win32-x64@0.33.5': optional: true - '@inquirer/confirm@3.1.6': + '@inquirer/confirm@5.0.2(@types/node@22.9.0)': dependencies: - '@inquirer/core': 8.1.0 - '@inquirer/type': 1.3.1 + '@inquirer/core': 10.1.0(@types/node@22.9.0) + '@inquirer/type': 3.0.1(@types/node@22.9.0) + '@types/node': 22.9.0 - '@inquirer/core@8.1.0': + '@inquirer/core@10.1.0(@types/node@22.9.0)': dependencies: - '@inquirer/figures': 1.0.1 - '@inquirer/type': 1.3.1 - '@types/mute-stream': 0.0.4 - '@types/node': 20.14.12 - '@types/wrap-ansi': 3.0.0 + '@inquirer/figures': 1.0.8 + '@inquirer/type': 3.0.1(@types/node@22.9.0) ansi-escapes: 4.3.2 - chalk: 4.1.2 - cli-spinners: 2.9.2 cli-width: 4.1.0 - mute-stream: 1.0.0 + mute-stream: 2.0.0 signal-exit: 4.1.0 strip-ansi: 6.0.1 wrap-ansi: 6.2.0 + yoctocolors-cjs: 2.1.2 + transitivePeerDependencies: + - '@types/node' - '@inquirer/figures@1.0.1': {} + '@inquirer/figures@1.0.8': {} - '@inquirer/type@1.3.1': {} + '@inquirer/type@3.0.1(@types/node@22.9.0)': + dependencies: + '@types/node': 22.9.0 '@ioredis/commands@1.2.0': {} @@ -13209,7 +12867,7 @@ snapshots: '@jest/console@29.7.0': dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 chalk: 4.1.2 jest-message-util: 29.7.0 jest-util: 29.7.0 @@ -13222,14 +12880,14 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 ansi-escapes: 4.3.2 chalk: 4.1.2 ci-info: 3.7.1 exit: 0.1.2 graceful-fs: 4.2.11 jest-changed-files: 29.7.0 - jest-config: 29.7.0(@types/node@20.14.12) + jest-config: 29.7.0(@types/node@22.9.0) jest-haste-map: 29.7.0 jest-message-util: 29.7.0 jest-regex-util: 29.6.3 @@ -13258,7 +12916,7 @@ snapshots: dependencies: '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 jest-mock: 29.7.0 '@jest/expect-utils@29.7.0': @@ -13276,7 +12934,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@sinonjs/fake-timers': 10.3.0 - '@types/node': 20.14.12 + '@types/node': 22.9.0 jest-message-util: 29.7.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -13297,8 +12955,8 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 - '@types/node': 20.14.12 + '@jridgewell/trace-mapping': 0.3.18 + '@types/node': 22.9.0 chalk: 4.1.2 collect-v8-coverage: 1.0.1 exit: 0.1.2 @@ -13347,7 +13005,7 @@ snapshots: dependencies: '@babel/core': 7.24.7 '@jest/types': 29.6.3 - '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/trace-mapping': 0.3.18 babel-plugin-istanbul: 6.1.1 chalk: 4.1.2 convert-source-map: 2.0.0 @@ -13368,19 +13026,19 @@ snapshots: '@jest/schemas': 29.6.3 '@types/istanbul-lib-coverage': 2.0.4 '@types/istanbul-reports': 3.0.1 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/yargs': 17.0.19 chalk: 4.1.2 - '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': + '@joshwooding/vite-plugin-react-docgen-typescript@0.3.0(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))': dependencies: glob: 7.2.3 glob-promise: 4.2.2(glob@7.2.3) magic-string: 0.27.0 - react-docgen-typescript: 2.2.2(typescript@5.6.2) - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + react-docgen-typescript: 2.2.2(typescript@5.6.3) + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 '@jridgewell/gen-mapping@0.3.5': dependencies: @@ -13397,10 +13055,17 @@ snapshots: '@jridgewell/gen-mapping': 0.3.5 '@jridgewell/trace-mapping': 0.3.25 + '@jridgewell/sourcemap-codec@1.4.14': {} + '@jridgewell/sourcemap-codec@1.4.15': {} '@jridgewell/sourcemap-codec@1.5.0': {} + '@jridgewell/trace-mapping@0.3.18': + dependencies: + '@jridgewell/resolve-uri': 3.1.0 + '@jridgewell/sourcemap-codec': 1.4.14 + '@jridgewell/trace-mapping@0.3.25': dependencies: '@jridgewell/resolve-uri': 3.1.0 @@ -13428,23 +13093,23 @@ snapshots: '@types/react': 18.0.28 react: 18.3.1 - '@microsoft/api-extractor-model@7.29.8(@types/node@20.14.12)': + '@microsoft/api-extractor-model@7.29.8(@types/node@22.9.0)': dependencies: '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 - '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@22.9.0) transitivePeerDependencies: - '@types/node' - '@microsoft/api-extractor@7.47.9(@types/node@20.14.12)': + '@microsoft/api-extractor@7.47.11(@types/node@22.9.0)': dependencies: - '@microsoft/api-extractor-model': 7.29.8(@types/node@20.14.12) + '@microsoft/api-extractor-model': 7.29.8(@types/node@22.9.0) '@microsoft/tsdoc': 0.15.0 '@microsoft/tsdoc-config': 0.17.0 - '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@22.9.0) '@rushstack/rig-package': 0.5.3 - '@rushstack/terminal': 0.14.2(@types/node@20.14.12) - '@rushstack/ts-command-line': 4.22.8(@types/node@20.14.12) + '@rushstack/terminal': 0.14.2(@types/node@22.9.0) + '@rushstack/ts-command-line': 4.23.0(@types/node@22.9.0) lodash: 4.17.21 minimatch: 3.0.8 resolve: 1.22.8 @@ -13465,14 +13130,14 @@ snapshots: '@misskey-dev/browser-image-resizer@2024.1.0': {} - '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2))(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0))(eslint@9.8.0)(globals@15.9.0)': + '@misskey-dev/eslint-plugin@2.0.3(@eslint/compat@1.1.1)(@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3))(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0))(eslint@9.14.0)(globals@15.12.0)': dependencies: '@eslint/compat': 1.1.1 - '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2) - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) - eslint: 9.8.0 - eslint-plugin-import: 2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0) - globals: 15.9.0 + '@typescript-eslint/eslint-plugin': 7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3) + eslint: 9.14.0 + eslint-plugin-import: 2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0) + globals: 15.12.0 '@misskey-dev/sharp-read-bmp@1.2.0': dependencies: @@ -13520,16 +13185,7 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64@3.0.2': optional: true - '@mswjs/interceptors@0.29.1': - dependencies: - '@open-draft/deferred-promise': 2.2.0 - '@open-draft/logger': 0.3.0 - '@open-draft/until': 2.1.0 - is-node-process: 1.2.0 - outvariant: 1.4.2 - strict-event-emitter: 0.5.1 - - '@mswjs/interceptors@0.35.9': + '@mswjs/interceptors@0.36.10': dependencies: '@open-draft/deferred-promise': 2.2.0 '@open-draft/logger': 0.3.0 @@ -13577,7 +13233,7 @@ snapshots: '@napi-rs/canvas-linux-x64-musl': 0.1.56 '@napi-rs/canvas-win32-x64-msvc': 0.1.56 - '@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: iterare: 1.2.1 reflect-metadata: 0.2.2 @@ -13585,9 +13241,9 @@ snapshots: tslib: 2.7.0 uid: 2.0.2 - '@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)': + '@nestjs/core@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1)': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) '@nuxtjs/opencollective': 0.3.2(encoding@0.1.13) fast-safe-stringify: 2.1.1 iterare: 1.2.1 @@ -13597,31 +13253,31 @@ snapshots: tslib: 2.7.0 uid: 2.0.2 optionalDependencies: - '@nestjs/platform-express': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) transitivePeerDependencies: - encoding - '@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3)': + '@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7)': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) body-parser: 1.20.3 cors: 2.8.5 - express: 4.21.0 + express: 4.21.1 multer: 1.4.4-lts.1 tslib: 2.7.0 transitivePeerDependencies: - supports-color - '@nestjs/testing@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3))': + '@nestjs/testing@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7))': dependencies: - '@nestjs/common': 10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1) - '@nestjs/core': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.3)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/common': 10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1) + '@nestjs/core': 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/platform-express@10.4.7)(encoding@0.1.13)(reflect-metadata@0.2.2)(rxjs@7.8.1) tslib: 2.7.0 optionalDependencies: - '@nestjs/platform-express': 10.4.3(@nestjs/common@10.4.3(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.3) + '@nestjs/platform-express': 10.4.7(@nestjs/common@10.4.7(reflect-metadata@0.2.2)(rxjs@7.8.1))(@nestjs/core@10.4.7) - '@noble/hashes@1.4.0': {} + '@noble/hashes@1.5.0': {} '@nodelib/fs.scandir@2.1.5': dependencies: @@ -13647,7 +13303,7 @@ snapshots: '@npmcli/fs@3.1.0': dependencies: - semver: 7.6.0 + semver: 7.6.3 '@nuxtjs/opencollective@0.3.2(encoding@0.1.13)': dependencies: @@ -13664,7 +13320,7 @@ snapshots: '@open-draft/logger@0.3.0': dependencies: is-node-process: 1.2.0 - outvariant: 1.4.2 + outvariant: 1.4.3 '@open-draft/until@2.1.0': {} @@ -13672,214 +13328,298 @@ snapshots: dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/api@1.9.0': {} + '@opentelemetry/api-logs@0.53.0': + dependencies: + '@opentelemetry/api': 1.9.0 - '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/api-logs@0.54.2': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core@1.24.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/api@1.9.0': {} + + '@opentelemetry/context-async-hooks@1.25.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/semantic-conventions': 1.24.1 '@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/semantic-conventions': 1.25.1 - '@opentelemetry/instrumentation-connect@0.38.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/core@1.26.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/core@1.28.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/semantic-conventions': 1.27.0 + + '@opentelemetry/instrumentation-amqplib@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - '@types/connect': 3.4.36 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-express@0.41.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-connect@0.40.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@types/connect': 3.4.36 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-fastify@0.38.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-dataloader@0.12.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-express@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-graphql@0.42.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fastify@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-hapi@0.40.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-fs@0.16.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-http@0.52.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-generic-pool@0.39.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-graphql@0.44.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-hapi@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - semver: 7.6.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-ioredis@0.42.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-http@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/core': 1.26.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 + semver: 7.6.3 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-ioredis@0.43.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/semantic-conventions': 1.28.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-kafkajs@0.4.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-knex@0.41.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-koa@0.42.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-koa@0.43.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongodb@0.46.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-lru-memoizer@0.40.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-metrics': 1.24.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-mongodb@0.48.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mongoose@0.40.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mongoose@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql2@0.40.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql2@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-mysql@0.40.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-mysql@0.41.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - '@types/mysql': 2.15.22 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@types/mysql': 2.15.26 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-nestjs-core@0.39.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-nestjs-core@0.40.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-pg@0.43.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-pg@0.44.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 '@opentelemetry/sql-common': 0.40.1(@opentelemetry/api@1.9.0) '@types/pg': 8.6.1 - '@types/pg-pool': 2.0.4 + '@types/pg-pool': 2.0.6 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation-redis-4@0.41.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-redis-4@0.42.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) '@opentelemetry/redis-common': 0.36.2 - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/semantic-conventions': 1.28.0 transitivePeerDependencies: - supports-color - '@opentelemetry/instrumentation@0.46.0(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation-tedious@0.15.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@types/shimmer': 1.0.5 - import-in-the-middle: 1.7.1 - require-in-the-middle: 7.3.0 - semver: 7.6.0 - shimmer: 1.2.1 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@types/tedious': 4.0.14 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/instrumentation-undici@0.6.0(@opentelemetry/api@1.9.0)': + dependencies: + '@opentelemetry/api': 1.9.0 + '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.53.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color - optional: true '@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/api-logs': 0.52.1 '@types/shimmer': 1.0.5 - import-in-the-middle: 1.10.0 + import-in-the-middle: 1.11.2 require-in-the-middle: 7.3.0 - semver: 7.6.0 + semver: 7.6.3 shimmer: 1.2.1 transitivePeerDependencies: - supports-color - '@opentelemetry/redis-common@0.36.2': {} - - '@opentelemetry/resources@1.24.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.53.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.24.1 + '@opentelemetry/api-logs': 0.53.0 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.11.2 + require-in-the-middle: 7.3.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color - '@opentelemetry/resources@1.25.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 + '@opentelemetry/api-logs': 0.54.2 + '@types/shimmer': 1.2.0 + import-in-the-middle: 1.11.2 + require-in-the-middle: 7.3.0 + semver: 7.6.3 + shimmer: 1.2.1 + transitivePeerDependencies: + - supports-color + + '@opentelemetry/redis-common@0.36.2': {} - '@opentelemetry/sdk-metrics@1.24.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/resources@1.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.24.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.24.1(@opentelemetry/api@1.9.0) - lodash.merge: 4.6.2 + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 - '@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0)': + '@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 - '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - - '@opentelemetry/semantic-conventions@1.24.1': {} + '@opentelemetry/core': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.27.0 '@opentelemetry/semantic-conventions@1.25.1': {} + '@opentelemetry/semantic-conventions@1.27.0': {} + + '@opentelemetry/semantic-conventions@1.28.0': {} + '@opentelemetry/sql-common@0.40.1(@opentelemetry/api@1.9.0)': dependencies: '@opentelemetry/api': 1.9.0 @@ -13889,27 +13629,27 @@ snapshots: dependencies: '@peculiar/asn1-schema': 2.3.8 asn1js: 3.0.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/asn1-ecc@2.3.8': dependencies: '@peculiar/asn1-schema': 2.3.8 '@peculiar/asn1-x509': 2.3.8 asn1js: 3.0.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/asn1-rsa@2.3.8': dependencies: '@peculiar/asn1-schema': 2.3.8 '@peculiar/asn1-x509': 2.3.8 asn1js: 3.0.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/asn1-schema@2.3.8': dependencies: asn1js: 3.0.5 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peculiar/asn1-x509@2.3.8': dependencies: @@ -13917,7 +13657,7 @@ snapshots: asn1js: 3.0.5 ipaddr.js: 2.2.0 pvtsutils: 1.3.5 - tslib: 2.6.3 + tslib: 2.7.0 '@peertube/http-signature@1.7.0': dependencies: @@ -13932,11 +13672,11 @@ snapshots: '@pkgjs/parseargs@0.11.0': optional: true - '@prisma/instrumentation@5.17.0': + '@prisma/instrumentation@5.19.1': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) transitivePeerDependencies: - supports-color @@ -13972,80 +13712,86 @@ snapshots: '@readme/openapi-schemas@3.1.0': {} - '@rollup/plugin-json@6.1.0(rollup@4.22.5)': + '@rollup/plugin-json@6.1.0(rollup@4.26.0)': dependencies: - '@rollup/pluginutils': 5.1.2(rollup@4.22.5) + '@rollup/pluginutils': 5.1.3(rollup@4.26.0) optionalDependencies: - rollup: 4.22.5 + rollup: 4.26.0 - '@rollup/plugin-replace@5.0.7(rollup@4.22.5)': + '@rollup/plugin-replace@5.0.7(rollup@4.26.0)': dependencies: - '@rollup/pluginutils': 5.1.2(rollup@4.22.5) + '@rollup/pluginutils': 5.1.3(rollup@4.26.0) magic-string: 0.30.10 optionalDependencies: - rollup: 4.22.5 + rollup: 4.26.0 - '@rollup/pluginutils@5.1.2(rollup@4.22.5)': + '@rollup/pluginutils@5.1.3(rollup@4.26.0)': dependencies: '@types/estree': 1.0.6 estree-walker: 2.0.2 - picomatch: 2.3.1 + picomatch: 4.0.2 optionalDependencies: - rollup: 4.22.5 + rollup: 4.26.0 + + '@rollup/rollup-android-arm-eabi@4.26.0': + optional: true - '@rollup/rollup-android-arm-eabi@4.22.5': + '@rollup/rollup-android-arm64@4.26.0': optional: true - '@rollup/rollup-android-arm64@4.22.5': + '@rollup/rollup-darwin-arm64@4.26.0': optional: true - '@rollup/rollup-darwin-arm64@4.22.5': + '@rollup/rollup-darwin-x64@4.26.0': optional: true - '@rollup/rollup-darwin-x64@4.22.5': + '@rollup/rollup-freebsd-arm64@4.26.0': optional: true - '@rollup/rollup-linux-arm-gnueabihf@4.22.5': + '@rollup/rollup-freebsd-x64@4.26.0': optional: true - '@rollup/rollup-linux-arm-musleabihf@4.22.5': + '@rollup/rollup-linux-arm-gnueabihf@4.26.0': optional: true - '@rollup/rollup-linux-arm64-gnu@4.22.5': + '@rollup/rollup-linux-arm-musleabihf@4.26.0': optional: true - '@rollup/rollup-linux-arm64-musl@4.22.5': + '@rollup/rollup-linux-arm64-gnu@4.26.0': optional: true - '@rollup/rollup-linux-powerpc64le-gnu@4.22.5': + '@rollup/rollup-linux-arm64-musl@4.26.0': optional: true - '@rollup/rollup-linux-riscv64-gnu@4.22.5': + '@rollup/rollup-linux-powerpc64le-gnu@4.26.0': optional: true - '@rollup/rollup-linux-s390x-gnu@4.22.5': + '@rollup/rollup-linux-riscv64-gnu@4.26.0': optional: true - '@rollup/rollup-linux-x64-gnu@4.22.5': + '@rollup/rollup-linux-s390x-gnu@4.26.0': optional: true - '@rollup/rollup-linux-x64-musl@4.22.5': + '@rollup/rollup-linux-x64-gnu@4.26.0': optional: true - '@rollup/rollup-win32-arm64-msvc@4.22.5': + '@rollup/rollup-linux-x64-musl@4.26.0': optional: true - '@rollup/rollup-win32-ia32-msvc@4.22.5': + '@rollup/rollup-win32-arm64-msvc@4.26.0': optional: true - '@rollup/rollup-win32-x64-msvc@4.22.5': + '@rollup/rollup-win32-ia32-msvc@4.26.0': + optional: true + + '@rollup/rollup-win32-x64-msvc@4.26.0': optional: true '@rtsao/scc@1.1.0': {} '@ruffle-rs/ruffle@0.1.0-nightly.2024.10.15': {} - '@rushstack/node-core-library@5.9.0(@types/node@20.14.12)': + '@rushstack/node-core-library@5.9.0(@types/node@22.9.0)': dependencies: ajv: 8.13.0 ajv-draft-04: 1.0.0(ajv@8.13.0) @@ -14056,23 +13802,23 @@ snapshots: resolve: 1.22.8 semver: 7.5.4 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@rushstack/rig-package@0.5.3': dependencies: resolve: 1.22.8 strip-json-comments: 3.1.1 - '@rushstack/terminal@0.14.2(@types/node@20.14.12)': + '@rushstack/terminal@0.14.2(@types/node@22.9.0)': dependencies: - '@rushstack/node-core-library': 5.9.0(@types/node@20.14.12) + '@rushstack/node-core-library': 5.9.0(@types/node@22.9.0) supports-color: 8.1.1 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 - '@rushstack/ts-command-line@4.22.8(@types/node@20.14.12)': + '@rushstack/ts-command-line@4.23.0(@types/node@22.9.0)': dependencies: - '@rushstack/terminal': 0.14.2(@types/node@20.14.12) + '@rushstack/terminal': 0.14.2(@types/node@22.9.0) '@types/argparse': 1.0.38 argparse: 1.0.10 string-argv: 0.3.1 @@ -14081,77 +13827,107 @@ snapshots: '@sec-ant/readable-stream@0.4.1': {} - '@sentry/core@8.20.0': + '@sentry/core@8.38.0': dependencies: - '@sentry/types': 8.20.0 - '@sentry/utils': 8.20.0 + '@sentry/types': 8.38.0 + '@sentry/utils': 8.38.0 - '@sentry/node@8.20.0': + '@sentry/node@8.38.0': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/context-async-hooks': 1.25.1(@opentelemetry/api@1.9.0) '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-connect': 0.38.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-express': 0.41.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-fastify': 0.38.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-graphql': 0.42.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-hapi': 0.40.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-http': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-ioredis': 0.42.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-koa': 0.42.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongodb': 0.46.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mongoose': 0.40.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql': 0.40.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-mysql2': 0.40.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-nestjs-core': 0.39.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-pg': 0.43.0(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation-redis-4': 0.41.0(@opentelemetry/api@1.9.0) - '@opentelemetry/resources': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - '@prisma/instrumentation': 5.17.0 - '@sentry/core': 8.20.0 - '@sentry/opentelemetry': 8.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.25.1) - '@sentry/types': 8.20.0 - '@sentry/utils': 8.20.0 - import-in-the-middle: 1.10.0 - optionalDependencies: - opentelemetry-instrumentation-fetch-node: 1.2.3(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-amqplib': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-connect': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-dataloader': 0.12.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-express': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fastify': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-fs': 0.16.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-generic-pool': 0.39.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-graphql': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-hapi': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-http': 0.53.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-ioredis': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-kafkajs': 0.4.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-knex': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-koa': 0.43.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-lru-memoizer': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongodb': 0.48.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mongoose': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-mysql2': 0.41.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-nestjs-core': 0.40.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-pg': 0.44.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-redis-4': 0.42.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-tedious': 0.15.0(@opentelemetry/api@1.9.0) + '@opentelemetry/instrumentation-undici': 0.6.0(@opentelemetry/api@1.9.0) + '@opentelemetry/resources': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@prisma/instrumentation': 5.19.1 + '@sentry/core': 8.38.0 + '@sentry/opentelemetry': 8.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0) + '@sentry/types': 8.38.0 + '@sentry/utils': 8.38.0 + import-in-the-middle: 1.11.2 transitivePeerDependencies: - supports-color - '@sentry/opentelemetry@8.20.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.52.1(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.25.1)': + '@sentry/opentelemetry@8.38.0(@opentelemetry/api@1.9.0)(@opentelemetry/core@1.25.1(@opentelemetry/api@1.9.0))(@opentelemetry/instrumentation@0.54.2(@opentelemetry/api@1.9.0))(@opentelemetry/sdk-trace-base@1.28.0(@opentelemetry/api@1.9.0))(@opentelemetry/semantic-conventions@1.28.0)': dependencies: '@opentelemetry/api': 1.9.0 '@opentelemetry/core': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/instrumentation': 0.52.1(@opentelemetry/api@1.9.0) - '@opentelemetry/sdk-trace-base': 1.25.1(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - '@sentry/core': 8.20.0 - '@sentry/types': 8.20.0 - '@sentry/utils': 8.20.0 + '@opentelemetry/instrumentation': 0.54.2(@opentelemetry/api@1.9.0) + '@opentelemetry/sdk-trace-base': 1.28.0(@opentelemetry/api@1.9.0) + '@opentelemetry/semantic-conventions': 1.28.0 + '@sentry/core': 8.38.0 + '@sentry/types': 8.38.0 + '@sentry/utils': 8.38.0 - '@sentry/profiling-node@8.20.0': + '@sentry/profiling-node@8.38.0': dependencies: - '@sentry/core': 8.20.0 - '@sentry/node': 8.20.0 - '@sentry/types': 8.20.0 - '@sentry/utils': 8.20.0 + '@sentry/core': 8.38.0 + '@sentry/node': 8.38.0 + '@sentry/types': 8.38.0 + '@sentry/utils': 8.38.0 detect-libc: 2.0.3 node-abi: 3.62.0 transitivePeerDependencies: - supports-color - '@sentry/types@8.20.0': {} + '@sentry/types@8.38.0': {} - '@sentry/utils@8.20.0': + '@sentry/utils@8.38.0': dependencies: - '@sentry/types': 8.20.0 + '@sentry/types': 8.38.0 - '@shikijs/core@1.12.0': + '@shikijs/core@1.22.2': dependencies: + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 + hast-util-to-html: 9.0.3 + + '@shikijs/engine-javascript@1.22.2': + dependencies: + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + oniguruma-to-js: 0.4.3 + + '@shikijs/engine-oniguruma@1.22.2': + dependencies: + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 + + '@shikijs/types@1.22.2': + dependencies: + '@shikijs/vscode-textmate': 9.3.0 + '@types/hast': 3.0.4 + + '@shikijs/vscode-textmate@9.3.0': {} '@sideway/address@4.1.4': dependencies: @@ -14181,15 +13957,15 @@ snapshots: '@simplewebauthn/types@10.0.0': {} + '@simplewebauthn/types@11.0.0': {} + '@sinclair/typebox@0.27.8': {} '@sindresorhus/is@4.6.0': {} '@sindresorhus/is@5.3.0': {} - '@sindresorhus/is@7.0.0': {} - - '@sindresorhus/merge-streams@4.0.0': {} + '@sindresorhus/is@7.0.1': {} '@sinonjs/commons@2.0.0': dependencies: @@ -14218,21 +13994,21 @@ snapshots: '@smithy/abort-controller@2.2.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/abort-controller@3.1.1': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/chunked-blob-reader-native@3.0.0': dependencies: '@smithy/util-base64': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/chunked-blob-reader@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/config-resolver@3.0.5': dependencies: @@ -14240,7 +14016,7 @@ snapshots: '@smithy/types': 3.3.0 '@smithy/util-config-provider': 3.0.0 '@smithy/util-middleware': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/core@2.3.1': dependencies: @@ -14251,7 +14027,7 @@ snapshots: '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/credential-provider-imds@3.2.0': dependencies: @@ -14259,37 +14035,37 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/eventstream-codec@3.1.2': dependencies: '@aws-crypto/crc32': 5.2.0 '@smithy/types': 3.3.0 '@smithy/util-hex-encoding': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/eventstream-serde-browser@3.0.5': dependencies: '@smithy/eventstream-serde-universal': 3.0.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/eventstream-serde-config-resolver@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/eventstream-serde-node@3.0.4': dependencies: '@smithy/eventstream-serde-universal': 3.0.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/eventstream-serde-universal@3.0.4': dependencies: '@smithy/eventstream-codec': 3.1.2 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/fetch-http-handler@3.2.4': dependencies: @@ -14297,52 +14073,52 @@ snapshots: '@smithy/querystring-builder': 3.0.3 '@smithy/types': 3.3.0 '@smithy/util-base64': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/hash-blob-browser@3.1.2': dependencies: '@smithy/chunked-blob-reader': 3.0.0 '@smithy/chunked-blob-reader-native': 3.0.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/hash-node@3.0.3': dependencies: '@smithy/types': 3.3.0 '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/hash-stream-node@3.1.2': dependencies: '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/invalid-dependency@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/is-array-buffer@2.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/is-array-buffer@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/md5-js@3.0.3': dependencies: '@smithy/types': 3.3.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/middleware-content-length@3.0.5': dependencies: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/middleware-endpoint@3.1.0': dependencies: @@ -14352,7 +14128,7 @@ snapshots: '@smithy/types': 3.3.0 '@smithy/url-parser': 3.0.3 '@smithy/util-middleware': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/middleware-retry@3.0.13': dependencies: @@ -14363,25 +14139,25 @@ snapshots: '@smithy/types': 3.3.0 '@smithy/util-middleware': 3.0.3 '@smithy/util-retry': 3.0.3 - tslib: 2.6.3 + tslib: 2.7.0 uuid: 9.0.1 '@smithy/middleware-serde@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/middleware-stack@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/node-config-provider@3.1.4': dependencies: '@smithy/property-provider': 3.1.3 '@smithy/shared-ini-file-loader': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/node-http-handler@2.5.0': dependencies: @@ -14397,39 +14173,39 @@ snapshots: '@smithy/protocol-http': 4.1.0 '@smithy/querystring-builder': 3.0.3 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/property-provider@3.1.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/protocol-http@3.3.0': dependencies: '@smithy/types': 2.12.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/protocol-http@4.1.0': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/querystring-builder@2.2.0': dependencies: '@smithy/types': 2.12.0 '@smithy/util-uri-escape': 2.2.0 - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/querystring-builder@3.0.3': dependencies: '@smithy/types': 3.3.0 '@smithy/util-uri-escape': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/querystring-parser@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/service-error-classification@3.0.3': dependencies: @@ -14438,7 +14214,7 @@ snapshots: '@smithy/shared-ini-file-loader@3.1.4': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/signature-v4@4.1.0': dependencies: @@ -14449,7 +14225,7 @@ snapshots: '@smithy/util-middleware': 3.0.3 '@smithy/util-uri-escape': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/smithy-client@3.1.11': dependencies: @@ -14458,49 +14234,49 @@ snapshots: '@smithy/protocol-http': 4.1.0 '@smithy/types': 3.3.0 '@smithy/util-stream': 3.1.3 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/types@2.12.0': dependencies: - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/types@3.3.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/url-parser@3.0.3': dependencies: '@smithy/querystring-parser': 3.0.3 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-base64@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-body-length-browser@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-body-length-node@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-buffer-from@2.0.0': dependencies: '@smithy/is-array-buffer': 2.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-buffer-from@3.0.0': dependencies: '@smithy/is-array-buffer': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-config-provider@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-defaults-mode-browser@3.0.13': dependencies: @@ -14508,7 +14284,7 @@ snapshots: '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 bowser: 2.11.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-defaults-mode-node@3.0.13': dependencies: @@ -14518,28 +14294,28 @@ snapshots: '@smithy/property-provider': 3.1.3 '@smithy/smithy-client': 3.1.11 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-endpoints@2.0.5': dependencies: '@smithy/node-config-provider': 3.1.4 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-hex-encoding@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-middleware@3.0.3': dependencies: '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-retry@3.0.3': dependencies: '@smithy/service-error-classification': 3.0.3 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-stream@3.1.3': dependencies: @@ -14550,217 +14326,194 @@ snapshots: '@smithy/util-buffer-from': 3.0.0 '@smithy/util-hex-encoding': 3.0.0 '@smithy/util-utf8': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-uri-escape@2.2.0': dependencies: - tslib: 2.6.2 + tslib: 2.7.0 '@smithy/util-uri-escape@3.0.0': dependencies: - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-utf8@2.0.0': dependencies: '@smithy/util-buffer-from': 2.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-utf8@3.0.0': dependencies: '@smithy/util-buffer-from': 3.0.0 - tslib: 2.6.3 + tslib: 2.7.0 '@smithy/util-waiter@3.1.2': dependencies: '@smithy/abort-controller': 3.1.1 '@smithy/types': 3.3.0 - tslib: 2.6.3 + tslib: 2.7.0 '@sqltools/formatter@1.2.5': {} - '@storybook/addon-actions@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-actions@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 '@types/uuid': 9.0.8 dequal: 2.0.3 polished: 4.2.2 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) uuid: 9.0.1 - '@storybook/addon-backgrounds@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-backgrounds@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 memoizerific: 1.11.3 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-controls@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-controls@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 dequal: 2.0.3 - lodash: 4.17.21 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-docs@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-docs@8.4.4(@types/react@18.0.28)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@mdx-js/react': 3.0.1(@types/react@18.0.28)(react@18.3.1) - '@storybook/blocks': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/csf-plugin': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/global': 5.0.0 - '@storybook/react-dom-shim': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@types/react': 18.0.28 - fs-extra: 11.1.1 + '@storybook/blocks': 8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/csf-plugin': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/react-dom-shim': 8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - rehype-external-links: 3.0.0 - rehype-slug: 6.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' - '@storybook/addon-essentials@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-essentials@8.4.4(@types/react@18.0.28)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - '@storybook/addon-actions': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-backgrounds': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-controls': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-docs': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-highlight': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-measure': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-outline': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-toolbars': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/addon-viewport': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@storybook/addon-actions': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-backgrounds': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-controls': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-docs': 8.4.4(@types/react@18.0.28)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-highlight': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-measure': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-outline': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-toolbars': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/addon-viewport': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 + transitivePeerDependencies: + - '@types/react' - '@storybook/addon-highlight@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-highlight@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/addon-interactions@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-interactions@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/test': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/instrumenter': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/test': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) polished: 4.2.2 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-links@8.3.3(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-links@8.4.4(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 '@storybook/global': 5.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 optionalDependencies: react: 18.3.1 - '@storybook/addon-mdx-gfm@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-mdx-gfm@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: remark-gfm: 4.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 transitivePeerDependencies: - supports-color - '@storybook/addon-measure@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-measure@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) tiny-invariant: 1.3.3 - '@storybook/addon-outline@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-outline@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - '@storybook/addon-storysource@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-storysource@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - '@storybook/source-loader': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/source-loader': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) estraverse: 5.3.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) tiny-invariant: 1.3.3 - '@storybook/addon-toolbars@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-toolbars@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/addon-viewport@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/addon-viewport@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: memoizerific: 1.11.3 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/blocks@8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/blocks@8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 - '@storybook/global': 5.0.0 '@storybook/icons': 1.2.12(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - '@types/lodash': 4.14.191 - color-convert: 2.0.1 - dequal: 2.0.3 - lodash: 4.17.21 - markdown-to-jsx: 7.4.7(react@18.3.1) - memoizerific: 1.11.3 - polished: 4.2.2 - react-colorful: 5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - telejson: 7.2.0 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - util-deprecate: 1.0.2 optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/builder-vite@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': + '@storybook/builder-vite@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))': dependencies: - '@storybook/csf-plugin': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@types/find-cache-dir': 3.2.1 + '@storybook/csf-plugin': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) browser-assert: 1.2.1 - es-module-lexer: 1.5.4 - express: 4.19.2 - find-cache-dir: 3.3.2 - fs-extra: 11.1.1 - magic-string: 0.30.10 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) - optionalDependencies: - typescript: 5.6.2 - transitivePeerDependencies: - - supports-color + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) - '@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/components@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/core-events@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/core@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)': + '@storybook/core@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)': dependencies: '@storybook/csf': 0.1.11 - '@types/express': 4.17.21 better-opn: 3.0.2 browser-assert: 1.2.1 - esbuild: 0.23.1 - esbuild-register: 3.5.0(esbuild@0.23.1) - express: 4.19.2 + esbuild: 0.24.0 + esbuild-register: 3.5.0(esbuild@0.24.0) jsdoc-type-pratt-parser: 4.1.0 process: 0.11.10 recast: 0.23.6 semver: 7.6.3 util: 0.12.5 ws: 8.18.0(bufferutil@4.0.8)(utf-8-validate@6.0.4) + optionalDependencies: + prettier: 3.3.3 transitivePeerDependencies: - bufferutil - supports-color - utf-8-validate - '@storybook/csf-plugin@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/csf-plugin@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) unplugin: 1.4.0 '@storybook/csf@0.1.11': @@ -14774,143 +14527,122 @@ snapshots: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - '@storybook/instrumenter@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/instrumenter@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/global': 5.0.0 '@vitest/utils': 2.1.2 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - util: 0.12.5 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/manager-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/preview-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/react-dom-shim@8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/react-dom-shim@8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/react-vite@8.3.3(@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.22.5)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))': + '@storybook/react-vite@8.4.4(@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(rollup@4.26.0)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))': dependencies: - '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) - '@rollup/pluginutils': 5.1.2(rollup@4.22.5) - '@storybook/builder-vite': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) - '@storybook/react': 8.3.3(@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2) + '@joshwooding/vite-plugin-react-docgen-typescript': 0.3.0(typescript@5.6.3)(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)) + '@rollup/pluginutils': 5.1.3(rollup@4.26.0) + '@storybook/builder-vite': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)) + '@storybook/react': 8.4.4(@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(typescript@5.6.3) find-up: 5.0.0 - magic-string: 0.30.10 + magic-string: 0.30.11 react: 18.3.1 react-docgen: 7.0.1 react-dom: 18.3.1(react@18.3.1) resolve: 1.22.8 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) tsconfig-paths: 4.2.0 - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) transitivePeerDependencies: - - '@preact/preset-vite' - '@storybook/test' - rollup - supports-color - typescript - - vite-plugin-glimmerx - '@storybook/react@8.3.3(@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)': + '@storybook/react@8.4.4(@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(typescript@5.6.3)': dependencies: - '@storybook/components': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/components': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/react-dom-shim': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@types/escodegen': 0.0.6 - '@types/estree': 0.0.51 - '@types/node': 22.7.5 - acorn: 7.4.1 - acorn-jsx: 5.3.2(acorn@7.4.1) - acorn-walk: 7.2.0 - escodegen: 2.1.0 - html-tags: 3.2.0 - prop-types: 15.8.1 + '@storybook/manager-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/react-dom-shim': 8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - react-element-to-jsx-string: 15.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1) - semver: 7.6.0 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - ts-dedent: 2.2.0 - type-fest: 2.19.0 - util-deprecate: 1.0.2 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) optionalDependencies: - '@storybook/test': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - typescript: 5.6.2 + '@storybook/test': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + typescript: 5.6.3 - '@storybook/source-loader@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/source-loader@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 + es-toolkit: 1.27.0 estraverse: 5.3.0 - lodash: 4.17.21 prettier: 3.3.3 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/test@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/test@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: '@storybook/csf': 0.1.11 '@storybook/global': 5.0.0 - '@storybook/instrumenter': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/instrumenter': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@testing-library/dom': 10.4.0 '@testing-library/jest-dom': 6.5.0 '@testing-library/user-event': 14.5.2(@testing-library/dom@10.4.0) '@vitest/expect': 2.0.5 '@vitest/spy': 2.0.5 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - util: 0.12.5 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/theming@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/types@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))': + '@storybook/types@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))': dependencies: - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) - '@storybook/vue3-vite@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2))': + '@storybook/vue3-vite@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': dependencies: - '@storybook/builder-vite': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(typescript@5.6.2)(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0)) - '@storybook/vue3': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(typescript@5.6.2)) + '@storybook/builder-vite': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0)) + '@storybook/vue3': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vue@3.5.12(typescript@5.6.3)) find-package-json: 1.2.0 - magic-string: 0.30.10 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) - typescript: 5.6.2 - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) - vue-component-meta: 2.0.16(typescript@5.6.2) - vue-docgen-api: 4.75.1(vue@3.5.10(typescript@5.6.2)) + magic-string: 0.30.11 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) + typescript: 5.6.3 + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) + vue-component-meta: 2.0.16(typescript@5.6.3) + vue-docgen-api: 4.75.1(vue@3.5.12(typescript@5.6.3)) transitivePeerDependencies: - - '@preact/preset-vite' - - supports-color - - vite-plugin-glimmerx - vue - '@storybook/vue3@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4))(vue@3.5.10(typescript@5.6.2))': + '@storybook/vue3@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4))(vue@3.5.12(typescript@5.6.3))': dependencies: - '@storybook/components': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/components': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) '@storybook/global': 5.0.0 - '@storybook/manager-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@vue/compiler-core': 3.4.37 - storybook: 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@storybook/manager-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@vue/compiler-core': 3.5.11 + storybook: 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) ts-dedent: 2.2.0 type-fest: 2.19.0 - vue: 3.5.10(typescript@5.6.2) + vue: 3.5.12(typescript@5.6.3) vue-component-type-helpers: 2.1.10 - '@swc/cli@0.3.12(@swc/core@1.6.6)(chokidar@3.5.3)': + '@swc/cli@0.3.12(@swc/core@1.9.2)(chokidar@3.5.3)': dependencies: '@mole-inc/bin-wrapper': 8.0.1 - '@swc/core': 1.6.6 + '@swc/core': 1.9.2 '@swc/counter': 0.1.3 commander: 8.3.0 fast-glob: 3.3.2 @@ -14930,19 +14662,13 @@ snapshots: '@swc/core-darwin-arm64@1.3.56': optional: true - '@swc/core-darwin-arm64@1.6.6': - optional: true - - '@swc/core-darwin-arm64@1.7.36': + '@swc/core-darwin-arm64@1.9.2': optional: true '@swc/core-darwin-x64@1.3.56': optional: true - '@swc/core-darwin-x64@1.6.6': - optional: true - - '@swc/core-darwin-x64@1.7.36': + '@swc/core-darwin-x64@1.9.2': optional: true '@swc/core-freebsd-x64@1.3.11': @@ -14953,128 +14679,77 @@ snapshots: '@swc/core-linux-arm-gnueabihf@1.3.56': optional: true - '@swc/core-linux-arm-gnueabihf@1.6.6': - optional: true - - '@swc/core-linux-arm-gnueabihf@1.7.36': + '@swc/core-linux-arm-gnueabihf@1.9.2': optional: true '@swc/core-linux-arm64-gnu@1.3.56': optional: true - '@swc/core-linux-arm64-gnu@1.6.6': - optional: true - - '@swc/core-linux-arm64-gnu@1.7.36': + '@swc/core-linux-arm64-gnu@1.9.2': optional: true '@swc/core-linux-arm64-musl@1.3.56': optional: true - '@swc/core-linux-arm64-musl@1.6.6': - optional: true - - '@swc/core-linux-arm64-musl@1.7.36': + '@swc/core-linux-arm64-musl@1.9.2': optional: true '@swc/core-linux-x64-gnu@1.3.56': optional: true - '@swc/core-linux-x64-gnu@1.6.6': - optional: true - - '@swc/core-linux-x64-gnu@1.7.36': + '@swc/core-linux-x64-gnu@1.9.2': optional: true '@swc/core-linux-x64-musl@1.3.56': optional: true - '@swc/core-linux-x64-musl@1.6.6': - optional: true - - '@swc/core-linux-x64-musl@1.7.36': + '@swc/core-linux-x64-musl@1.9.2': optional: true '@swc/core-win32-arm64-msvc@1.3.56': optional: true - '@swc/core-win32-arm64-msvc@1.6.6': - optional: true - - '@swc/core-win32-arm64-msvc@1.7.36': + '@swc/core-win32-arm64-msvc@1.9.2': optional: true '@swc/core-win32-ia32-msvc@1.3.56': optional: true - '@swc/core-win32-ia32-msvc@1.6.6': - optional: true - - '@swc/core-win32-ia32-msvc@1.7.36': + '@swc/core-win32-ia32-msvc@1.9.2': optional: true '@swc/core-win32-x64-msvc@1.3.56': optional: true - '@swc/core-win32-x64-msvc@1.6.6': - optional: true - - '@swc/core-win32-x64-msvc@1.7.36': + '@swc/core-win32-x64-msvc@1.9.2': optional: true - '@swc/core@1.6.6': + '@swc/core@1.9.2': dependencies: '@swc/counter': 0.1.3 - '@swc/types': 0.1.9 + '@swc/types': 0.1.17 optionalDependencies: - '@swc/core-darwin-arm64': 1.6.6 - '@swc/core-darwin-x64': 1.6.6 - '@swc/core-linux-arm-gnueabihf': 1.6.6 - '@swc/core-linux-arm64-gnu': 1.6.6 - '@swc/core-linux-arm64-musl': 1.6.6 - '@swc/core-linux-x64-gnu': 1.6.6 - '@swc/core-linux-x64-musl': 1.6.6 - '@swc/core-win32-arm64-msvc': 1.6.6 - '@swc/core-win32-ia32-msvc': 1.6.6 - '@swc/core-win32-x64-msvc': 1.6.6 - - '@swc/core@1.7.36': - dependencies: - '@swc/counter': 0.1.3 - '@swc/types': 0.1.13 - optionalDependencies: - '@swc/core-darwin-arm64': 1.7.36 - '@swc/core-darwin-x64': 1.7.36 - '@swc/core-linux-arm-gnueabihf': 1.7.36 - '@swc/core-linux-arm64-gnu': 1.7.36 - '@swc/core-linux-arm64-musl': 1.7.36 - '@swc/core-linux-x64-gnu': 1.7.36 - '@swc/core-linux-x64-musl': 1.7.36 - '@swc/core-win32-arm64-msvc': 1.7.36 - '@swc/core-win32-ia32-msvc': 1.7.36 - '@swc/core-win32-x64-msvc': 1.7.36 + '@swc/core-darwin-arm64': 1.9.2 + '@swc/core-darwin-x64': 1.9.2 + '@swc/core-linux-arm-gnueabihf': 1.9.2 + '@swc/core-linux-arm64-gnu': 1.9.2 + '@swc/core-linux-arm64-musl': 1.9.2 + '@swc/core-linux-x64-gnu': 1.9.2 + '@swc/core-linux-x64-musl': 1.9.2 + '@swc/core-win32-arm64-msvc': 1.9.2 + '@swc/core-win32-ia32-msvc': 1.9.2 + '@swc/core-win32-x64-msvc': 1.9.2 '@swc/counter@0.1.3': {} - '@swc/jest@0.2.36(@swc/core@1.6.6)': - dependencies: - '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.6.6 - '@swc/counter': 0.1.3 - jsonc-parser: 3.2.0 - - '@swc/jest@0.2.36(@swc/core@1.7.36)': + '@swc/jest@0.2.37(@swc/core@1.9.2)': dependencies: '@jest/create-cache-key-function': 29.7.0 - '@swc/core': 1.7.36 + '@swc/core': 1.9.2 '@swc/counter': 0.1.3 jsonc-parser: 3.2.0 - '@swc/types@0.1.13': - dependencies: - '@swc/counter': 0.1.3 - - '@swc/types@0.1.9': + '@swc/types@0.1.17': dependencies: '@swc/counter': 0.1.3 @@ -15131,14 +14806,14 @@ snapshots: dependencies: '@testing-library/dom': 10.4.0 - '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.10)(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))': + '@testing-library/vue@8.1.0(@vue/compiler-sfc@3.5.12)(@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))': dependencies: '@babel/runtime': 7.23.4 '@testing-library/dom': 9.3.4 - '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2)) - vue: 3.5.10(typescript@5.6.2) + '@vue/test-utils': 2.4.1(@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3)) + vue: 3.5.12(typescript@5.6.3) optionalDependencies: - '@vue/compiler-sfc': 3.5.10 + '@vue/compiler-sfc': 3.5.12 transitivePeerDependencies: - '@vue/server-renderer' @@ -15160,9 +14835,9 @@ snapshots: '@types/accepts@1.3.7': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 - '@types/archiver@6.0.2': + '@types/archiver@6.0.3': dependencies: '@types/readdir-glob': 1.1.1 @@ -15172,31 +14847,31 @@ snapshots: '@types/babel__core@7.20.0': dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 '@types/babel__generator': 7.6.4 '@types/babel__template': 7.4.1 '@types/babel__traverse': 7.20.0 '@types/babel__generator@7.6.4': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 '@types/babel__template@7.4.1': dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 '@types/babel__traverse@7.20.0': dependencies: - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 '@types/bcryptjs@2.4.6': {} '@types/body-parser@1.19.5': dependencies: '@types/connect': 3.4.35 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/braces@3.0.1': {} @@ -15204,10 +14879,12 @@ snapshots: dependencies: '@types/http-cache-semantics': 4.0.4 '@types/keyv': 3.1.4 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/responselike': 1.0.0 - '@types/color-convert@2.0.3': + '@types/canvas-confetti@1.6.4': {} + + '@types/color-convert@2.0.4': dependencies: '@types/color-name': 1.1.1 @@ -15215,11 +14892,11 @@ snapshots: '@types/connect@3.4.35': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/connect@3.4.36': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/content-disposition@0.5.8': {} @@ -15235,20 +14912,16 @@ snapshots: '@types/doctrine@0.0.9': {} - '@types/escodegen@0.0.6': {} - '@types/eslint@7.29.0': dependencies: '@types/estree': 1.0.6 '@types/json-schema': 7.0.15 - '@types/estree@0.0.51': {} - '@types/estree@1.0.6': {} '@types/express-serve-static-core@4.17.33': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/qs': 6.9.7 '@types/range-parser': 1.2.4 @@ -15259,31 +14932,22 @@ snapshots: '@types/qs': 6.9.7 '@types/serve-static': 1.15.1 - '@types/express@4.17.21': - dependencies: - '@types/body-parser': 1.19.5 - '@types/express-serve-static-core': 4.17.33 - '@types/qs': 6.9.7 - '@types/serve-static': 1.15.1 - - '@types/find-cache-dir@3.2.1': {} - - '@types/fluent-ffmpeg@2.1.26': + '@types/fluent-ffmpeg@2.1.27': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/form-data@2.5.0': dependencies: - form-data: 4.0.0 + form-data: 4.0.1 '@types/glob@7.2.0': dependencies: '@types/minimatch': 5.1.2 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/graceful-fs@4.1.6': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/hast@3.0.4': dependencies: @@ -15295,7 +14959,7 @@ snapshots: '@types/http-link-header@1.0.7': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/istanbul-lib-coverage@2.0.4': {} @@ -15312,7 +14976,7 @@ snapshots: expect: 29.7.0 pretty-format: 29.7.0 - '@types/jest@29.5.13': + '@types/jest@29.5.14': dependencies: expect: 29.7.0 pretty-format: 29.7.0 @@ -15321,9 +14985,9 @@ snapshots: '@types/jsdom@21.1.7': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/tough-cookie': 4.0.2 - parse5: 7.1.2 + parse5: 7.2.1 '@types/json-schema@7.0.12': {} @@ -15339,11 +15003,7 @@ snapshots: '@types/keyv@3.1.4': dependencies: - '@types/node': 20.14.12 - - '@types/lodash@4.14.191': {} - - '@types/matter-js@0.19.6': {} + '@types/node': 22.9.0 '@types/matter-js@0.19.7': {} @@ -15367,33 +15027,17 @@ snapshots: '@types/ms@0.7.34': {} - '@types/mute-stream@0.0.4': - dependencies: - '@types/node': 20.14.12 - - '@types/mysql@2.15.22': - dependencies: - '@types/node': 20.14.12 - - '@types/node@20.11.5': - dependencies: - undici-types: 5.26.5 - - '@types/node@20.14.12': + '@types/mysql@2.15.26': dependencies: - undici-types: 5.26.5 + '@types/node': 22.9.0 - '@types/node@20.9.1': - dependencies: - undici-types: 5.26.5 - - '@types/node@22.7.5': + '@types/node@22.9.0': dependencies: undici-types: 6.19.8 '@types/nodemailer@6.4.16': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/normalize-package-data@2.4.1': {} @@ -15404,37 +15048,41 @@ snapshots: '@types/oauth2orize@1.11.5': dependencies: '@types/express': 4.17.17 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/oauth@0.9.5': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 + + '@types/oauth@0.9.6': + dependencies: + '@types/node': 22.9.0 '@types/object-assign-deep@0.4.3': {} '@types/parse-link-header@2.0.3': {} - '@types/pg-pool@2.0.4': + '@types/pg-pool@2.0.6': dependencies: '@types/pg': 8.11.10 '@types/pg@8.11.10': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 pg-protocol: 1.6.1 pg-types: 4.0.1 '@types/pg@8.6.1': dependencies: - '@types/node': 20.14.12 - pg-protocol: 1.6.1 + '@types/node': 22.9.0 + pg-protocol: 1.7.0 pg-types: 2.2.0 '@types/prop-types@15.7.5': {} '@types/proxy-addr@2.0.3': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/psl@1.1.3': {} @@ -15444,7 +15092,7 @@ snapshots: '@types/qrcode@1.5.5': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/qs@6.9.7': {} @@ -15462,7 +15110,7 @@ snapshots: '@types/readdir-glob@1.1.1': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/rename@1.0.7': {} @@ -15470,7 +15118,7 @@ snapshots: '@types/responselike@1.0.0': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/sanitize-html@2.13.0': dependencies: @@ -15485,12 +15133,14 @@ snapshots: '@types/serve-static@1.15.1': dependencies: '@types/mime': 3.0.1 - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/serviceworker@0.0.67': {} '@types/shimmer@1.0.5': {} + '@types/shimmer@1.2.0': {} + '@types/simple-oauth2@5.0.7': {} '@types/sinon@10.0.13': @@ -15509,6 +15159,10 @@ snapshots: '@types/statuses@2.0.4': {} + '@types/tedious@4.0.14': + dependencies: + '@types/node': 22.9.0 + '@types/throttle-debounce@5.0.2': {} '@types/tinycolor2@1.4.6': {} @@ -15527,21 +15181,19 @@ snapshots: '@types/vary@1.1.3': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 - '@types/web-push@3.6.3': + '@types/web-push@3.6.4': dependencies: - '@types/node': 20.14.12 - - '@types/wrap-ansi@3.0.0': {} + '@types/node': 22.9.0 '@types/ws@8.5.11': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 - '@types/ws@8.5.12': + '@types/ws@8.5.13': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 '@types/yargs-parser@21.0.0': {} @@ -15551,7 +15203,7 @@ snapshots: '@types/yauzl@2.10.0': dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 optional: true '@typescript-eslint/eslint-plugin@6.21.0(@typescript-eslint/parser@6.21.0(eslint@8.57.0)(typescript@5.1.6))(eslint@8.57.0)(typescript@5.1.6)': @@ -15562,7 +15214,7 @@ snapshots: '@typescript-eslint/type-utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) eslint: 8.57.0 graphemer: 1.4.0 ignore: 5.3.1 @@ -15574,59 +15226,41 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3))(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/eslint-plugin@7.1.0(@typescript-eslint/parser@7.1.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.6.2 - '@typescript-eslint/parser': 7.1.0(eslint@9.8.0)(typescript@5.3.3) + '@typescript-eslint/parser': 7.1.0(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 7.1.0 - '@typescript-eslint/type-utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) - '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) + '@typescript-eslint/type-utils': 7.1.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/utils': 7.1.0(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + debug: 4.3.4 + eslint: 9.14.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.3.3) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4))(eslint@9.8.0)(typescript@5.5.4)': - dependencies: - '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) - '@typescript-eslint/visitor-keys': 7.17.0 - eslint: 9.8.0 - graphemer: 1.4.0 - ignore: 5.3.1 - natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0)(typescript@5.6.2)': + '@typescript-eslint/eslint-plugin@7.17.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0)(typescript@5.6.3)': dependencies: '@eslint-community/regexpp': 4.11.0 - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/type-utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/type-utils': 7.17.0(eslint@9.14.0)(typescript@5.6.3) + '@typescript-eslint/utils': 7.17.0(eslint@9.14.0)(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.17.0 - eslint: 9.8.0 + eslint: 9.14.0 graphemer: 1.4.0 ignore: 5.3.1 natural-compare: 1.4.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15636,49 +15270,36 @@ snapshots: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) eslint: 8.57.0 optionalDependencies: typescript: 5.1.6 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/parser@7.1.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.1.0 '@typescript-eslint/types': 7.1.0 - '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + debug: 4.3.4 + eslint: 9.14.0 optionalDependencies: - typescript: 5.3.3 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: '@typescript-eslint/scope-manager': 7.17.0 '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 - optionalDependencies: - typescript: 5.5.4 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2)': - dependencies: - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) - '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 + debug: 4.3.5(supports-color@5.5.0) + eslint: 9.14.0 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15701,7 +15322,7 @@ snapshots: dependencies: '@typescript-eslint/typescript-estree': 6.21.0(typescript@5.1.6) '@typescript-eslint/utils': 6.21.0(eslint@8.57.0)(typescript@5.1.6) - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) eslint: 8.57.0 ts-api-utils: 1.3.0(typescript@5.1.6) optionalDependencies: @@ -15709,39 +15330,27 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)': - dependencies: - '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) - '@typescript-eslint/utils': 7.1.0(eslint@9.8.0)(typescript@5.3.3) - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 - ts-api-utils: 1.3.0(typescript@5.3.3) - optionalDependencies: - typescript: 5.3.3 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)': + '@typescript-eslint/type-utils@7.1.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.5.4) - debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 - ts-api-utils: 1.3.0(typescript@5.5.4) + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) + '@typescript-eslint/utils': 7.1.0(eslint@9.14.0)(typescript@5.6.3) + debug: 4.3.4 + eslint: 9.14.0 + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.5.4 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/type-utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)': + '@typescript-eslint/type-utils@7.17.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) - '@typescript-eslint/utils': 7.17.0(eslint@9.8.0)(typescript@5.6.2) - debug: 4.3.5(supports-color@8.1.1) - eslint: 9.8.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) + '@typescript-eslint/utils': 7.17.0(eslint@9.14.0)(typescript@5.6.3) + debug: 4.3.5(supports-color@5.5.0) + eslint: 9.14.0 + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15755,7 +15364,7 @@ snapshots: dependencies: '@typescript-eslint/types': 6.21.0 '@typescript-eslint/visitor-keys': 6.21.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 @@ -15766,48 +15375,33 @@ snapshots: transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.1.0(typescript@5.3.3)': + '@typescript-eslint/typescript-estree@7.1.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.1.0 '@typescript-eslint/visitor-keys': 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.3 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.3.3) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.3.3 + typescript: 5.6.3 transitivePeerDependencies: - supports-color - '@typescript-eslint/typescript-estree@7.17.0(typescript@5.5.4)': + '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.3)': dependencies: '@typescript-eslint/types': 7.17.0 '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@8.1.1) - globby: 11.1.0 - is-glob: 4.0.3 - minimatch: 9.0.4 - semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.5.4) - optionalDependencies: - typescript: 5.5.4 - transitivePeerDependencies: - - supports-color - - '@typescript-eslint/typescript-estree@7.17.0(typescript@5.6.2)': - dependencies: - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/visitor-keys': 7.17.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) globby: 11.1.0 is-glob: 4.0.3 minimatch: 9.0.4 semver: 7.6.0 - ts-api-utils: 1.3.0(typescript@5.6.2) + ts-api-utils: 1.3.0(typescript@5.6.3) optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 transitivePeerDependencies: - supports-color @@ -15825,38 +15419,27 @@ snapshots: - supports-color - typescript - '@typescript-eslint/utils@7.1.0(eslint@9.8.0)(typescript@5.3.3)': + '@typescript-eslint/utils@7.1.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0) '@types/json-schema': 7.0.12 '@types/semver': 7.5.8 '@typescript-eslint/scope-manager': 7.1.0 '@typescript-eslint/types': 7.1.0 - '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.3.3) - eslint: 9.8.0 + '@typescript-eslint/typescript-estree': 7.1.0(typescript@5.6.3) + eslint: 9.14.0 semver: 7.6.0 transitivePeerDependencies: - supports-color - typescript - '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.5.4)': - dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - '@typescript-eslint/scope-manager': 7.17.0 - '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.5.4) - eslint: 9.8.0 - transitivePeerDependencies: - - supports-color - - typescript - - '@typescript-eslint/utils@7.17.0(eslint@9.8.0)(typescript@5.6.2)': + '@typescript-eslint/utils@7.17.0(eslint@9.14.0)(typescript@5.6.3)': dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) + '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0) '@typescript-eslint/scope-manager': 7.17.0 '@typescript-eslint/types': 7.17.0 - '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.2) - eslint: 9.8.0 + '@typescript-eslint/typescript-estree': 7.17.0(typescript@5.6.3) + eslint: 9.14.0 transitivePeerDependencies: - supports-color - typescript @@ -15878,16 +15461,40 @@ snapshots: '@ungap/structured-clone@1.2.0': {} - '@vitejs/plugin-vue@5.1.4(vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0))(vue@3.5.10(typescript@5.6.2))': + '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': + dependencies: + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) + vue: 3.5.12(typescript@5.6.3) + + '@vitejs/plugin-vue@5.2.0(vite@5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0))(vue@3.5.12(typescript@5.6.3))': + dependencies: + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) + vue: 3.5.12(typescript@5.6.3) + + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0))': dependencies: - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) - vue: 3.5.10(typescript@5.6.2) + '@ampproject/remapping': 2.2.1 + '@bcoe/v8-coverage': 0.2.3 + debug: 4.3.5(supports-color@5.5.0) + istanbul-lib-coverage: 3.2.2 + istanbul-lib-report: 3.0.1 + istanbul-lib-source-maps: 5.0.4 + istanbul-reports: 3.1.6 + magic-string: 0.30.10 + magicast: 0.3.4 + picocolors: 1.0.1 + std-env: 3.7.0 + strip-literal: 2.1.0 + test-exclude: 6.0.0 + vitest: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0) + transitivePeerDependencies: + - supports-color - '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0))': + '@vitest/coverage-v8@1.6.0(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0))': dependencies: '@ampproject/remapping': 2.2.1 '@bcoe/v8-coverage': 0.2.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 istanbul-lib-report: 3.0.1 istanbul-lib-source-maps: 5.0.4 @@ -15898,7 +15505,7 @@ snapshots: std-env: 3.7.0 strip-literal: 2.1.0 test-exclude: 6.0.0 - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) + vitest: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0) transitivePeerDependencies: - supports-color @@ -15931,7 +15538,7 @@ snapshots: '@vitest/snapshot@1.6.0': dependencies: - magic-string: 0.30.10 + magic-string: 0.30.11 pathe: 1.1.2 pretty-format: 29.7.0 @@ -15967,172 +15574,133 @@ snapshots: dependencies: '@volar/source-map': 2.2.0 - '@volar/language-core@2.4.6': + '@volar/language-core@2.4.10': dependencies: - '@volar/source-map': 2.4.6 + '@volar/source-map': 2.4.10 '@volar/source-map@2.2.0': dependencies: muggle-string: 0.4.1 - '@volar/source-map@2.4.6': {} + '@volar/source-map@2.4.10': {} '@volar/typescript@2.2.0': dependencies: '@volar/language-core': 2.2.0 path-browserify: 1.0.1 - '@volar/typescript@2.4.6': + '@volar/typescript@2.4.10': dependencies: - '@volar/language-core': 2.4.6 + '@volar/language-core': 2.4.10 path-browserify: 1.0.1 vscode-uri: 3.0.8 - '@vue/compiler-core@3.4.37': + '@vue/compiler-core@3.5.11': dependencies: - '@babel/parser': 7.24.7 - '@vue/shared': 3.4.37 - entities: 5.0.0 + '@babel/parser': 7.25.7 + '@vue/shared': 3.5.11 + entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 + source-map-js: 1.2.1 - '@vue/compiler-core@3.5.10': + '@vue/compiler-core@3.5.12': dependencies: '@babel/parser': 7.25.7 - '@vue/shared': 3.5.10 + '@vue/shared': 3.5.12 entities: 4.5.0 estree-walker: 2.0.2 - source-map-js: 1.2.0 - - '@vue/compiler-dom@3.4.37': - dependencies: - '@vue/compiler-core': 3.4.37 - '@vue/shared': 3.4.37 + source-map-js: 1.2.1 - '@vue/compiler-dom@3.5.10': + '@vue/compiler-dom@3.5.11': dependencies: - '@vue/compiler-core': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-core': 3.5.11 + '@vue/shared': 3.5.11 - '@vue/compiler-sfc@3.4.37': + '@vue/compiler-dom@3.5.12': dependencies: - '@babel/parser': 7.24.7 - '@vue/compiler-core': 3.4.37 - '@vue/compiler-dom': 3.4.37 - '@vue/compiler-ssr': 3.4.37 - '@vue/shared': 3.4.37 - estree-walker: 2.0.2 - magic-string: 0.30.10 - postcss: 8.4.47 - source-map-js: 1.2.0 + '@vue/compiler-core': 3.5.12 + '@vue/shared': 3.5.12 - '@vue/compiler-sfc@3.5.10': + '@vue/compiler-sfc@3.5.12': dependencies: '@babel/parser': 7.25.7 - '@vue/compiler-core': 3.5.10 - '@vue/compiler-dom': 3.5.10 - '@vue/compiler-ssr': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-core': 3.5.12 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 estree-walker: 2.0.2 magic-string: 0.30.11 - postcss: 8.4.47 - source-map-js: 1.2.0 - - '@vue/compiler-ssr@3.4.37': - dependencies: - '@vue/compiler-dom': 3.4.37 - '@vue/shared': 3.4.37 + postcss: 8.4.49 + source-map-js: 1.2.1 - '@vue/compiler-ssr@3.5.10': + '@vue/compiler-ssr@3.5.12': dependencies: - '@vue/compiler-dom': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/compiler-dom': 3.5.12 + '@vue/shared': 3.5.12 '@vue/compiler-vue2@2.7.16': dependencies: de-indent: 1.0.2 he: 1.2.0 - '@vue/language-core@2.0.16(typescript@5.6.2)': + '@vue/language-core@2.0.16(typescript@5.6.3)': dependencies: '@volar/language-core': 2.2.0 - '@vue/compiler-dom': 3.4.37 - '@vue/shared': 3.4.37 + '@vue/compiler-dom': 3.5.11 + '@vue/shared': 3.5.11 computeds: 0.0.1 minimatch: 9.0.4 path-browserify: 1.0.1 vue-template-compiler: 2.7.14 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 - '@vue/language-core@2.1.6(typescript@5.6.2)': + '@vue/language-core@2.1.10(typescript@5.6.3)': dependencies: - '@volar/language-core': 2.4.6 - '@vue/compiler-dom': 3.4.37 + '@volar/language-core': 2.4.10 + '@vue/compiler-dom': 3.5.11 '@vue/compiler-vue2': 2.7.16 - '@vue/shared': 3.4.37 - computeds: 0.0.1 + '@vue/shared': 3.5.11 + alien-signals: 0.2.2 minimatch: 9.0.4 muggle-string: 0.4.1 path-browserify: 1.0.1 optionalDependencies: - typescript: 5.6.2 - - '@vue/reactivity@3.4.37': - dependencies: - '@vue/shared': 3.4.37 + typescript: 5.6.3 - '@vue/reactivity@3.5.10': + '@vue/reactivity@3.5.12': dependencies: - '@vue/shared': 3.5.10 + '@vue/shared': 3.5.12 - '@vue/runtime-core@3.4.37': + '@vue/runtime-core@3.5.12': dependencies: - '@vue/reactivity': 3.4.37 - '@vue/shared': 3.4.37 + '@vue/reactivity': 3.5.12 + '@vue/shared': 3.5.12 - '@vue/runtime-core@3.5.10': + '@vue/runtime-dom@3.5.12': dependencies: - '@vue/reactivity': 3.5.10 - '@vue/shared': 3.5.10 - - '@vue/runtime-dom@3.4.37': - dependencies: - '@vue/reactivity': 3.4.37 - '@vue/runtime-core': 3.4.37 - '@vue/shared': 3.4.37 - csstype: 3.1.3 - - '@vue/runtime-dom@3.5.10': - dependencies: - '@vue/reactivity': 3.5.10 - '@vue/runtime-core': 3.5.10 - '@vue/shared': 3.5.10 + '@vue/reactivity': 3.5.12 + '@vue/runtime-core': 3.5.12 + '@vue/shared': 3.5.12 csstype: 3.1.3 - '@vue/server-renderer@3.4.37(vue@3.4.37(typescript@5.5.4))': - dependencies: - '@vue/compiler-ssr': 3.4.37 - '@vue/shared': 3.4.37 - vue: 3.4.37(typescript@5.5.4) - - '@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2))': + '@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3))': dependencies: - '@vue/compiler-ssr': 3.5.10 - '@vue/shared': 3.5.10 - vue: 3.5.10(typescript@5.6.2) + '@vue/compiler-ssr': 3.5.12 + '@vue/shared': 3.5.12 + vue: 3.5.12(typescript@5.6.3) - '@vue/shared@3.4.37': {} + '@vue/shared@3.5.11': {} - '@vue/shared@3.5.10': {} + '@vue/shared@3.5.12': {} - '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.10(vue@3.5.10(typescript@5.6.2)))(vue@3.5.10(typescript@5.6.2))': + '@vue/test-utils@2.4.1(@vue/server-renderer@3.5.12(vue@3.5.12(typescript@5.6.3)))(vue@3.5.12(typescript@5.6.3))': dependencies: js-beautify: 1.14.9 - vue: 3.5.10(typescript@5.6.2) + vue: 3.5.12(typescript@5.6.3) vue-component-type-helpers: 1.8.4 optionalDependencies: - '@vue/server-renderer': 3.5.10(vue@3.5.10(typescript@5.6.2)) + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3)) abbrev@1.1.1: {} @@ -16149,34 +15717,23 @@ snapshots: mime-types: 2.1.35 negotiator: 0.6.3 - acorn-import-assertions@1.9.0(acorn@8.12.1): + acorn-import-attributes@1.9.5(acorn@8.14.0): dependencies: - acorn: 8.12.1 - optional: true + acorn: 8.14.0 - acorn-import-attributes@1.9.5(acorn@8.12.1): + acorn-jsx@5.3.2(acorn@8.14.0): dependencies: - acorn: 8.12.1 - - acorn-jsx@5.3.2(acorn@7.4.1): - dependencies: - acorn: 7.4.1 - - acorn-jsx@5.3.2(acorn@8.12.1): - dependencies: - acorn: 8.12.1 - - acorn-walk@7.2.0: {} + acorn: 8.14.0 acorn-walk@8.3.2: {} acorn@7.4.1: {} - acorn@8.12.1: {} + acorn@8.14.0: {} agent-base@7.1.0: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -16239,6 +15796,8 @@ snapshots: json-schema-traverse: 1.0.0 require-from-string: 2.0.2 + alien-signals@0.2.2: {} + ansi-colors@4.1.3: {} ansi-escapes@4.3.2: @@ -16403,7 +15962,7 @@ snapshots: dependencies: pvtsutils: 1.3.5 pvutils: 1.1.3 - tslib: 2.6.3 + tslib: 2.7.0 assert-never@1.2.1: {} @@ -16415,7 +15974,7 @@ snapshots: ast-types@0.16.1: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 astral-regex@2.0.0: optional: true @@ -16469,7 +16028,7 @@ snapshots: axios@1.7.4: dependencies: follow-redirects: 1.15.9(debug@4.3.7) - form-data: 4.0.0 + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug @@ -16477,13 +16036,26 @@ snapshots: axios@1.7.7(debug@4.3.7): dependencies: follow-redirects: 1.15.9(debug@4.3.7) - form-data: 4.0.0 + form-data: 4.0.1 proxy-from-env: 1.1.0 transitivePeerDependencies: - debug b4a@1.6.4: {} + babel-jest@29.7.0(@babel/core@7.23.5): + dependencies: + '@babel/core': 7.23.5 + '@jest/transform': 29.7.0 + '@types/babel__core': 7.20.0 + babel-plugin-istanbul: 6.1.1 + babel-preset-jest: 29.6.3(@babel/core@7.23.5) + chalk: 4.1.2 + graceful-fs: 4.2.11 + slash: 3.0.0 + transitivePeerDependencies: + - supports-color + babel-jest@29.7.0(@babel/core@7.24.7): dependencies: '@babel/core': 7.24.7 @@ -16496,10 +16068,11 @@ snapshots: slash: 3.0.0 transitivePeerDependencies: - supports-color + optional: true babel-plugin-istanbul@6.1.1: dependencies: - '@babel/helper-plugin-utils': 7.24.7 + '@babel/helper-plugin-utils': 7.22.5 '@istanbuljs/load-nyc-config': 1.1.0 '@istanbuljs/schema': 0.1.3 istanbul-lib-instrument: 5.2.1 @@ -16509,11 +16082,27 @@ snapshots: babel-plugin-jest-hoist@29.6.3: dependencies: - '@babel/template': 7.24.7 - '@babel/types': 7.24.7 + '@babel/template': 7.24.0 + '@babel/types': 7.25.7 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.20.0 + babel-preset-current-node-syntax@1.0.1(@babel/core@7.23.5): + dependencies: + '@babel/core': 7.23.5 + '@babel/plugin-syntax-async-generators': 7.8.4(@babel/core@7.23.5) + '@babel/plugin-syntax-bigint': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-class-properties': 7.12.13(@babel/core@7.23.5) + '@babel/plugin-syntax-import-meta': 7.10.4(@babel/core@7.23.5) + '@babel/plugin-syntax-json-strings': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-logical-assignment-operators': 7.10.4(@babel/core@7.23.5) + '@babel/plugin-syntax-nullish-coalescing-operator': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-numeric-separator': 7.10.4(@babel/core@7.23.5) + '@babel/plugin-syntax-object-rest-spread': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.23.5) + '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.23.5) + babel-preset-current-node-syntax@1.0.1(@babel/core@7.24.7): dependencies: '@babel/core': 7.24.7 @@ -16529,12 +16118,20 @@ snapshots: '@babel/plugin-syntax-optional-catch-binding': 7.8.3(@babel/core@7.24.7) '@babel/plugin-syntax-optional-chaining': 7.8.3(@babel/core@7.24.7) '@babel/plugin-syntax-top-level-await': 7.14.5(@babel/core@7.24.7) + optional: true + + babel-preset-jest@29.6.3(@babel/core@7.23.5): + dependencies: + '@babel/core': 7.23.5 + babel-plugin-jest-hoist: 29.6.3 + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5) babel-preset-jest@29.6.3(@babel/core@7.24.7): dependencies: '@babel/core': 7.24.7 babel-plugin-jest-hoist: 29.6.3 babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) + optional: true babel-walk@3.0.0-canary-5: dependencies: @@ -16564,7 +16161,7 @@ snapshots: bin-version-check@5.0.0: dependencies: bin-version: 6.0.0 - semver: 7.6.0 + semver: 7.6.3 semver-truncate: 2.0.0 bin-version@6.0.0: @@ -16583,23 +16180,6 @@ snapshots: bn.js@4.12.0: {} - body-parser@1.20.2: - dependencies: - bytes: 3.1.2 - content-type: 1.0.5 - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - http-errors: 2.0.0 - iconv-lite: 0.4.24 - on-finished: 2.4.1 - qs: 6.11.0 - raw-body: 2.5.2 - type-is: 1.6.18 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - body-parser@1.20.3: dependencies: bytes: 3.1.2 @@ -16647,6 +16227,13 @@ snapshots: browser-assert@1.2.1: {} + browserslist@4.22.2: + dependencies: + caniuse-lite: 1.0.30001566 + electron-to-chromium: 1.4.601 + node-releases: 2.0.14 + update-browserslist-db: 1.0.13(browserslist@4.22.2) + browserslist@4.23.0: dependencies: caniuse-lite: 1.0.30001591 @@ -16697,14 +16284,14 @@ snapshots: node-gyp-build: 4.8.1 optional: true - bullmq@5.13.2: + bullmq@5.26.1: dependencies: cron-parser: 4.8.1 ioredis: 5.4.1 - msgpackr: 1.10.1 + msgpackr: 1.11.2 node-abort-controller: 3.1.1 - semver: 7.6.0 - tslib: 2.6.3 + semver: 7.6.3 + tslib: 2.7.0 uuid: 9.0.1 transitivePeerDependencies: - supports-color @@ -16805,6 +16392,8 @@ snapshots: lodash.memoize: 4.1.2 lodash.uniq: 4.5.0 + caniuse-lite@1.0.30001566: {} + caniuse-lite@1.0.30001591: {} canonicalize@1.0.8: {} @@ -16862,32 +16451,36 @@ snapshots: char-regex@1.0.2: {} + character-entities-html4@2.1.0: {} + + character-entities-legacy@3.0.0: {} + character-entities@2.0.2: {} character-parser@2.2.0: dependencies: is-regex: 1.1.4 - chart.js@4.4.4: + chart.js@4.4.6: dependencies: '@kurkle/color': 0.3.2 - chartjs-adapter-date-fns@3.0.0(chart.js@4.4.4)(date-fns@2.30.0): + chartjs-adapter-date-fns@3.0.0(chart.js@4.4.6)(date-fns@2.30.0): dependencies: - chart.js: 4.4.4 + chart.js: 4.4.6 date-fns: 2.30.0 - chartjs-chart-matrix@2.0.1(chart.js@4.4.4): + chartjs-chart-matrix@2.0.1(chart.js@4.4.6): dependencies: - chart.js: 4.4.4 + chart.js: 4.4.6 - chartjs-plugin-gradient@0.6.1(chart.js@4.4.4): + chartjs-plugin-gradient@0.6.1(chart.js@4.4.6): dependencies: - chart.js: 4.4.4 + chart.js: 4.4.6 - chartjs-plugin-zoom@2.0.1(chart.js@4.4.4): + chartjs-plugin-zoom@2.0.1(chart.js@4.4.6): dependencies: - chart.js: 4.4.4 + chart.js: 4.4.6 hammerjs: 2.0.8 check-error@1.0.3: @@ -16915,7 +16508,7 @@ snapshots: domutils: 3.1.0 encoding-sniffer: 0.2.0 htmlparser2: 9.1.0 - parse5: 7.1.2 + parse5: 7.2.1 parse5-htmlparser2-tree-adapter: 7.0.0 parse5-parser-stream: 7.1.2 undici: 6.20.0 @@ -16928,7 +16521,7 @@ snapshots: domhandler: 5.0.3 domutils: 3.0.1 htmlparser2: 8.0.1 - parse5: 7.1.2 + parse5: 7.2.1 parse5-htmlparser2-tree-adapter: 7.0.0 chokidar@3.5.3: @@ -16945,10 +16538,13 @@ snapshots: chownr@2.0.0: {} - chromatic@11.10.4: {} + chromatic@11.18.1: {} ci-info@3.7.1: {} + ci-info@4.1.0: + optional: true + cjs-module-lexer@1.2.2: {} clean-stack@2.2.0: {} @@ -16971,8 +16567,6 @@ snapshots: parse5-htmlparser2-tree-adapter: 6.0.1 yargs: 16.2.0 - cli-spinners@2.9.2: {} - cli-table3@0.6.3: dependencies: string-width: 4.2.3 @@ -17049,6 +16643,8 @@ snapshots: dependencies: delayed-stream: 1.0.0 + comma-separated-tokens@2.0.3: {} + commander@10.0.1: {} commander@12.1.0: {} @@ -17067,8 +16663,6 @@ snapshots: common-tags@1.8.2: optional: true - commondir@1.0.1: {} - compare-versions@6.1.1: {} compress-commons@6.0.2: @@ -17119,14 +16713,14 @@ snapshots: cookie-signature@1.0.6: {} - cookie-signature@1.2.1: {} - - cookie@0.5.0: {} - cookie@0.6.0: {} + cookie@0.7.1: {} + cookie@0.7.2: {} + cookie@1.0.2: {} + core-util-is@1.0.2: {} core-util-is@1.0.3: {} @@ -17143,13 +16737,13 @@ snapshots: crc-32: 1.2.2 readable-stream: 4.3.0 - create-jest@29.7.0(@types/node@20.14.12): + create-jest@29.7.0(@types/node@22.9.0): dependencies: '@jest/types': 29.6.3 chalk: 4.1.2 exit: 0.1.2 graceful-fs: 4.2.11 - jest-config: 29.7.0(@types/node@20.14.12) + jest-config: 29.7.0(@types/node@22.9.0) jest-util: 29.7.0 prompts: 2.4.2 transitivePeerDependencies: @@ -17195,9 +16789,9 @@ snapshots: shebang-command: 2.0.0 which: 2.0.2 - css-declaration-sorter@7.2.0(postcss@8.4.47): + css-declaration-sorter@7.2.0(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 css-select@5.1.0: dependencies: @@ -17223,49 +16817,49 @@ snapshots: cssesc@3.0.0: {} - cssnano-preset-default@6.1.2(postcss@8.4.47): + cssnano-preset-default@6.1.2(postcss@8.4.49): dependencies: browserslist: 4.23.0 - css-declaration-sorter: 7.2.0(postcss@8.4.47) - cssnano-utils: 4.0.2(postcss@8.4.47) - postcss: 8.4.47 - postcss-calc: 9.0.1(postcss@8.4.47) - postcss-colormin: 6.1.0(postcss@8.4.47) - postcss-convert-values: 6.1.0(postcss@8.4.47) - postcss-discard-comments: 6.0.2(postcss@8.4.47) - postcss-discard-duplicates: 6.0.3(postcss@8.4.47) - postcss-discard-empty: 6.0.3(postcss@8.4.47) - postcss-discard-overridden: 6.0.2(postcss@8.4.47) - postcss-merge-longhand: 6.0.5(postcss@8.4.47) - postcss-merge-rules: 6.1.1(postcss@8.4.47) - postcss-minify-font-values: 6.1.0(postcss@8.4.47) - postcss-minify-gradients: 6.0.3(postcss@8.4.47) - postcss-minify-params: 6.1.0(postcss@8.4.47) - postcss-minify-selectors: 6.0.4(postcss@8.4.47) - postcss-normalize-charset: 6.0.2(postcss@8.4.47) - postcss-normalize-display-values: 6.0.2(postcss@8.4.47) - postcss-normalize-positions: 6.0.2(postcss@8.4.47) - postcss-normalize-repeat-style: 6.0.2(postcss@8.4.47) - postcss-normalize-string: 6.0.2(postcss@8.4.47) - postcss-normalize-timing-functions: 6.0.2(postcss@8.4.47) - postcss-normalize-unicode: 6.1.0(postcss@8.4.47) - postcss-normalize-url: 6.0.2(postcss@8.4.47) - postcss-normalize-whitespace: 6.0.2(postcss@8.4.47) - postcss-ordered-values: 6.0.2(postcss@8.4.47) - postcss-reduce-initial: 6.1.0(postcss@8.4.47) - postcss-reduce-transforms: 6.0.2(postcss@8.4.47) - postcss-svgo: 6.0.3(postcss@8.4.47) - postcss-unique-selectors: 6.0.4(postcss@8.4.47) + css-declaration-sorter: 7.2.0(postcss@8.4.49) + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 + postcss-calc: 9.0.1(postcss@8.4.49) + postcss-colormin: 6.1.0(postcss@8.4.49) + postcss-convert-values: 6.1.0(postcss@8.4.49) + postcss-discard-comments: 6.0.2(postcss@8.4.49) + postcss-discard-duplicates: 6.0.3(postcss@8.4.49) + postcss-discard-empty: 6.0.3(postcss@8.4.49) + postcss-discard-overridden: 6.0.2(postcss@8.4.49) + postcss-merge-longhand: 6.0.5(postcss@8.4.49) + postcss-merge-rules: 6.1.1(postcss@8.4.49) + postcss-minify-font-values: 6.1.0(postcss@8.4.49) + postcss-minify-gradients: 6.0.3(postcss@8.4.49) + postcss-minify-params: 6.1.0(postcss@8.4.49) + postcss-minify-selectors: 6.0.4(postcss@8.4.49) + postcss-normalize-charset: 6.0.2(postcss@8.4.49) + postcss-normalize-display-values: 6.0.2(postcss@8.4.49) + postcss-normalize-positions: 6.0.2(postcss@8.4.49) + postcss-normalize-repeat-style: 6.0.2(postcss@8.4.49) + postcss-normalize-string: 6.0.2(postcss@8.4.49) + postcss-normalize-timing-functions: 6.0.2(postcss@8.4.49) + postcss-normalize-unicode: 6.1.0(postcss@8.4.49) + postcss-normalize-url: 6.0.2(postcss@8.4.49) + postcss-normalize-whitespace: 6.0.2(postcss@8.4.49) + postcss-ordered-values: 6.0.2(postcss@8.4.49) + postcss-reduce-initial: 6.1.0(postcss@8.4.49) + postcss-reduce-transforms: 6.0.2(postcss@8.4.49) + postcss-svgo: 6.0.3(postcss@8.4.49) + postcss-unique-selectors: 6.0.4(postcss@8.4.49) - cssnano-utils@4.0.2(postcss@8.4.47): + cssnano-utils@4.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - cssnano@6.1.2(postcss@8.4.47): + cssnano@6.1.2(postcss@8.4.49): dependencies: - cssnano-preset-default: 6.1.2(postcss@8.4.47) + cssnano-preset-default: 6.1.2(postcss@8.4.49) lilconfig: 3.1.1 - postcss: 8.4.47 + postcss: 8.4.49 csso@5.0.5: dependencies: @@ -17277,55 +16871,9 @@ snapshots: csstype@3.1.3: {} - cypress@13.14.2: - dependencies: - '@cypress/request': 3.0.5 - '@cypress/xvfb': 1.2.4(supports-color@8.1.1) - '@types/sinonjs__fake-timers': 8.1.1 - '@types/sizzle': 2.3.3 - arch: 2.2.0 - blob-util: 2.0.2 - bluebird: 3.7.2 - buffer: 5.7.1 - cachedir: 2.3.0 - chalk: 4.1.2 - check-more-types: 2.24.0 - cli-cursor: 3.1.0 - cli-table3: 0.6.3 - commander: 6.2.1 - common-tags: 1.8.2 - dayjs: 1.11.10 - debug: 4.3.5(supports-color@8.1.1) - enquirer: 2.3.6 - eventemitter2: 6.4.7 - execa: 4.1.0 - executable: 4.1.1 - extract-zip: 2.0.1(supports-color@8.1.1) - figures: 3.2.0 - fs-extra: 9.1.0 - getos: 3.2.1 - is-ci: 3.0.1 - is-installed-globally: 0.4.0 - lazy-ass: 1.6.0 - listr2: 3.14.0(enquirer@2.3.6) - lodash: 4.17.21 - log-symbols: 4.1.0 - minimist: 1.2.8 - ospath: 1.2.2 - pretty-bytes: 5.6.0 - process: 0.11.10 - proxy-from-env: 1.0.0 - request-progress: 3.0.0 - semver: 7.6.0 - supports-color: 8.1.1 - tmp: 0.2.3 - untildify: 4.0.0 - yauzl: 2.10.0 - optional: true - - cypress@13.15.0: + cypress@13.15.2: dependencies: - '@cypress/request': 3.0.5 + '@cypress/request': 3.0.6 '@cypress/xvfb': 1.2.4(supports-color@8.1.1) '@types/sinonjs__fake-timers': 8.1.1 '@types/sizzle': 2.3.3 @@ -17336,12 +16884,13 @@ snapshots: cachedir: 2.3.0 chalk: 4.1.2 check-more-types: 2.24.0 + ci-info: 4.1.0 cli-cursor: 3.1.0 cli-table3: 0.6.3 commander: 6.2.1 common-tags: 1.8.2 dayjs: 1.11.10 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) enquirer: 2.3.6 eventemitter2: 6.4.7 execa: 4.1.0 @@ -17350,7 +16899,6 @@ snapshots: figures: 3.2.0 fs-extra: 9.1.0 getos: 3.2.1 - is-ci: 3.0.1 is-installed-globally: 0.4.0 lazy-ass: 1.6.0 listr2: 3.14.0(enquirer@2.3.6) @@ -17362,9 +16910,10 @@ snapshots: process: 0.11.10 proxy-from-env: 1.0.0 request-progress: 3.0.0 - semver: 7.6.0 + semver: 7.6.3 supports-color: 8.1.1 tmp: 0.2.3 + tree-kill: 1.2.2 untildify: 4.0.0 yauzl: 2.10.0 optional: true @@ -17416,11 +16965,9 @@ snapshots: optionalDependencies: supports-color: 8.1.1 - debug@4.3.4(supports-color@5.5.0): + debug@4.3.4: dependencies: ms: 2.1.2 - optionalDependencies: - supports-color: 5.5.0 debug@4.3.5(supports-color@5.5.0): dependencies: @@ -17428,16 +16975,12 @@ snapshots: optionalDependencies: supports-color: 5.5.0 - debug@4.3.5(supports-color@8.1.1): + debug@4.3.7(supports-color@8.1.1): dependencies: - ms: 2.1.2 + ms: 2.1.3 optionalDependencies: supports-color: 8.1.1 - debug@4.3.7: - dependencies: - ms: 2.1.3 - decamelize-keys@1.1.1: dependencies: decamelize: 1.2.0 @@ -17640,7 +17183,7 @@ snapshots: '@one-ini/wasm': 0.1.1 commander: 10.0.1 minimatch: 9.0.1 - semver: 7.6.0 + semver: 7.6.3 ee-first@1.1.1: {} @@ -17648,6 +17191,8 @@ snapshots: dependencies: jake: 10.8.5 + electron-to-chromium@1.4.601: {} + electron-to-chromium@1.4.686: {} emittery@0.13.1: {} @@ -17683,8 +17228,6 @@ snapshots: entities@4.5.0: {} - entities@5.0.0: {} - env-paths@2.2.1: {} err-code@2.0.3: {} @@ -17802,8 +17345,6 @@ snapshots: isarray: 2.0.5 stop-iteration-iterator: 1.0.0 - es-module-lexer@1.5.4: {} - es-object-atoms@1.0.0: dependencies: es-errors: 1.3.0 @@ -17834,10 +17375,12 @@ snapshots: is-date-object: 1.0.5 is-symbol: 1.0.4 - esbuild-register@3.5.0(esbuild@0.23.1): + es-toolkit@1.27.0: {} + + esbuild-register@3.5.0(esbuild@0.24.0): dependencies: - debug: 4.3.5(supports-color@8.1.1) - esbuild: 0.23.1 + debug: 4.3.7(supports-color@8.1.1) + esbuild: 0.24.0 transitivePeerDependencies: - supports-color @@ -17866,32 +17409,6 @@ snapshots: '@esbuild/win32-ia32': 0.18.20 '@esbuild/win32-x64': 0.18.20 - esbuild@0.19.11: - optionalDependencies: - '@esbuild/aix-ppc64': 0.19.11 - '@esbuild/android-arm': 0.19.11 - '@esbuild/android-arm64': 0.19.11 - '@esbuild/android-x64': 0.19.11 - '@esbuild/darwin-arm64': 0.19.11 - '@esbuild/darwin-x64': 0.19.11 - '@esbuild/freebsd-arm64': 0.19.11 - '@esbuild/freebsd-x64': 0.19.11 - '@esbuild/linux-arm': 0.19.11 - '@esbuild/linux-arm64': 0.19.11 - '@esbuild/linux-ia32': 0.19.11 - '@esbuild/linux-loong64': 0.19.11 - '@esbuild/linux-mips64el': 0.19.11 - '@esbuild/linux-ppc64': 0.19.11 - '@esbuild/linux-riscv64': 0.19.11 - '@esbuild/linux-s390x': 0.19.11 - '@esbuild/linux-x64': 0.19.11 - '@esbuild/netbsd-x64': 0.19.11 - '@esbuild/openbsd-x64': 0.19.11 - '@esbuild/sunos-x64': 0.19.11 - '@esbuild/win32-arm64': 0.19.11 - '@esbuild/win32-ia32': 0.19.11 - '@esbuild/win32-x64': 0.19.11 - esbuild@0.21.5: optionalDependencies: '@esbuild/aix-ppc64': 0.21.5 @@ -17918,59 +17435,32 @@ snapshots: '@esbuild/win32-ia32': 0.21.5 '@esbuild/win32-x64': 0.21.5 - esbuild@0.23.0: - optionalDependencies: - '@esbuild/aix-ppc64': 0.23.0 - '@esbuild/android-arm': 0.23.0 - '@esbuild/android-arm64': 0.23.0 - '@esbuild/android-x64': 0.23.0 - '@esbuild/darwin-arm64': 0.23.0 - '@esbuild/darwin-x64': 0.23.0 - '@esbuild/freebsd-arm64': 0.23.0 - '@esbuild/freebsd-x64': 0.23.0 - '@esbuild/linux-arm': 0.23.0 - '@esbuild/linux-arm64': 0.23.0 - '@esbuild/linux-ia32': 0.23.0 - '@esbuild/linux-loong64': 0.23.0 - '@esbuild/linux-mips64el': 0.23.0 - '@esbuild/linux-ppc64': 0.23.0 - '@esbuild/linux-riscv64': 0.23.0 - '@esbuild/linux-s390x': 0.23.0 - '@esbuild/linux-x64': 0.23.0 - '@esbuild/netbsd-x64': 0.23.0 - '@esbuild/openbsd-arm64': 0.23.0 - '@esbuild/openbsd-x64': 0.23.0 - '@esbuild/sunos-x64': 0.23.0 - '@esbuild/win32-arm64': 0.23.0 - '@esbuild/win32-ia32': 0.23.0 - '@esbuild/win32-x64': 0.23.0 - - esbuild@0.23.1: + esbuild@0.24.0: optionalDependencies: - '@esbuild/aix-ppc64': 0.23.1 - '@esbuild/android-arm': 0.23.1 - '@esbuild/android-arm64': 0.23.1 - '@esbuild/android-x64': 0.23.1 - '@esbuild/darwin-arm64': 0.23.1 - '@esbuild/darwin-x64': 0.23.1 - '@esbuild/freebsd-arm64': 0.23.1 - '@esbuild/freebsd-x64': 0.23.1 - '@esbuild/linux-arm': 0.23.1 - '@esbuild/linux-arm64': 0.23.1 - '@esbuild/linux-ia32': 0.23.1 - '@esbuild/linux-loong64': 0.23.1 - '@esbuild/linux-mips64el': 0.23.1 - '@esbuild/linux-ppc64': 0.23.1 - '@esbuild/linux-riscv64': 0.23.1 - '@esbuild/linux-s390x': 0.23.1 - '@esbuild/linux-x64': 0.23.1 - '@esbuild/netbsd-x64': 0.23.1 - '@esbuild/openbsd-arm64': 0.23.1 - '@esbuild/openbsd-x64': 0.23.1 - '@esbuild/sunos-x64': 0.23.1 - '@esbuild/win32-arm64': 0.23.1 - '@esbuild/win32-ia32': 0.23.1 - '@esbuild/win32-x64': 0.23.1 + '@esbuild/aix-ppc64': 0.24.0 + '@esbuild/android-arm': 0.24.0 + '@esbuild/android-arm64': 0.24.0 + '@esbuild/android-x64': 0.24.0 + '@esbuild/darwin-arm64': 0.24.0 + '@esbuild/darwin-x64': 0.24.0 + '@esbuild/freebsd-arm64': 0.24.0 + '@esbuild/freebsd-x64': 0.24.0 + '@esbuild/linux-arm': 0.24.0 + '@esbuild/linux-arm64': 0.24.0 + '@esbuild/linux-ia32': 0.24.0 + '@esbuild/linux-loong64': 0.24.0 + '@esbuild/linux-mips64el': 0.24.0 + '@esbuild/linux-ppc64': 0.24.0 + '@esbuild/linux-riscv64': 0.24.0 + '@esbuild/linux-s390x': 0.24.0 + '@esbuild/linux-x64': 0.24.0 + '@esbuild/netbsd-x64': 0.24.0 + '@esbuild/openbsd-arm64': 0.24.0 + '@esbuild/openbsd-x64': 0.24.0 + '@esbuild/sunos-x64': 0.24.0 + '@esbuild/win32-arm64': 0.24.0 + '@esbuild/win32-ia32': 0.24.0 + '@esbuild/win32-x64': 0.24.0 escalade@3.1.1: {} @@ -17988,14 +17478,6 @@ snapshots: escape-string-regexp@5.0.0: {} - escodegen@2.1.0: - dependencies: - esprima: 4.0.1 - estraverse: 5.3.0 - esutils: 2.0.3 - optionalDependencies: - source-map: 0.6.1 - eslint-config-prettier@9.1.0(eslint@8.57.0): dependencies: eslint: 8.57.0 @@ -18019,17 +17501,17 @@ snapshots: transitivePeerDependencies: - supports-color - eslint-module-utils@2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0): + eslint-module-utils@2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0): dependencies: debug: 3.2.7(supports-color@8.1.1) optionalDependencies: - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) - eslint: 9.8.0 + '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3) + eslint: 9.14.0 eslint-import-resolver-node: 0.3.9 transitivePeerDependencies: - supports-color - eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint@9.8.0): + eslint-plugin-import@2.30.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0): dependencies: '@rtsao/scc': 1.1.0 array-includes: 3.1.8 @@ -18038,9 +17520,9 @@ snapshots: array.prototype.flatmap: 1.3.2 debug: 3.2.7(supports-color@8.1.1) doctrine: 2.1.0 - eslint: 9.8.0 + eslint: 9.14.0 eslint-import-resolver-node: 0.3.9 - eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.8.0)(typescript@5.6.2))(eslint-import-resolver-node@0.3.9)(eslint@9.8.0) + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0) hasown: 2.0.2 is-core-module: 2.15.1 is-glob: 4.0.3 @@ -18051,36 +17533,51 @@ snapshots: semver: 6.3.1 tsconfig-paths: 3.15.0 optionalDependencies: - '@typescript-eslint/parser': 7.17.0(eslint@9.8.0)(typescript@5.6.2) + '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3) transitivePeerDependencies: - eslint-import-resolver-typescript - eslint-import-resolver-webpack - supports-color - eslint-plugin-vue@9.27.0(eslint@9.8.0): + eslint-plugin-import@2.31.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint@9.14.0): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - eslint: 9.8.0 - globals: 13.24.0 - natural-compare: 1.4.0 - nth-check: 2.1.1 - postcss-selector-parser: 6.0.16 - semver: 7.6.0 - vue-eslint-parser: 9.4.3(eslint@9.8.0) - xml-name-validator: 4.0.0 + '@rtsao/scc': 1.1.0 + array-includes: 3.1.8 + array.prototype.findlastindex: 1.2.5 + array.prototype.flat: 1.3.2 + array.prototype.flatmap: 1.3.2 + debug: 3.2.7(supports-color@8.1.1) + doctrine: 2.1.0 + eslint: 9.14.0 + eslint-import-resolver-node: 0.3.9 + eslint-module-utils: 2.12.0(@typescript-eslint/parser@7.17.0(eslint@9.14.0)(typescript@5.6.3))(eslint-import-resolver-node@0.3.9)(eslint@9.14.0) + hasown: 2.0.2 + is-core-module: 2.15.1 + is-glob: 4.0.3 + minimatch: 3.1.2 + object.fromentries: 2.0.8 + object.groupby: 1.0.3 + object.values: 1.2.0 + semver: 6.3.1 + string.prototype.trimend: 1.0.8 + tsconfig-paths: 3.15.0 + optionalDependencies: + '@typescript-eslint/parser': 7.17.0(eslint@9.14.0)(typescript@5.6.3) transitivePeerDependencies: + - eslint-import-resolver-typescript + - eslint-import-resolver-webpack - supports-color - eslint-plugin-vue@9.28.0(eslint@9.8.0): + eslint-plugin-vue@9.31.0(eslint@9.14.0): dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - eslint: 9.8.0 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0) + eslint: 9.14.0 globals: 13.24.0 natural-compare: 1.4.0 nth-check: 2.1.1 postcss-selector-parser: 6.0.16 semver: 7.6.3 - vue-eslint-parser: 9.4.3(eslint@9.8.0) + vue-eslint-parser: 9.4.3(eslint@9.14.0) xml-name-validator: 4.0.0 transitivePeerDependencies: - supports-color @@ -18092,14 +17589,14 @@ snapshots: esrecurse: 4.3.0 estraverse: 5.3.0 - eslint-scope@8.0.2: + eslint-scope@8.2.0: dependencies: esrecurse: 4.3.0 estraverse: 5.3.0 eslint-visitor-keys@3.4.3: {} - eslint-visitor-keys@4.0.0: {} + eslint-visitor-keys@4.2.0: {} eslint@8.57.0: dependencies: @@ -18114,7 +17611,7 @@ snapshots: ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) doctrine: 3.0.0 escape-string-regexp: 4.0.0 eslint-scope: 7.2.2 @@ -18144,24 +17641,28 @@ snapshots: transitivePeerDependencies: - supports-color - eslint@9.8.0: + eslint@9.14.0: dependencies: - '@eslint-community/eslint-utils': 4.4.0(eslint@9.8.0) - '@eslint-community/regexpp': 4.11.0 - '@eslint/config-array': 0.17.1 + '@eslint-community/eslint-utils': 4.4.0(eslint@9.14.0) + '@eslint-community/regexpp': 4.12.1 + '@eslint/config-array': 0.18.0 + '@eslint/core': 0.7.0 '@eslint/eslintrc': 3.1.0 - '@eslint/js': 9.8.0 + '@eslint/js': 9.14.0 + '@eslint/plugin-kit': 0.2.3 + '@humanfs/node': 0.16.6 '@humanwhocodes/module-importer': 1.0.1 - '@humanwhocodes/retry': 0.3.0 - '@nodelib/fs.walk': 1.2.8 + '@humanwhocodes/retry': 0.4.1 + '@types/estree': 1.0.6 + '@types/json-schema': 7.0.15 ajv: 6.12.6 chalk: 4.1.2 cross-spawn: 7.0.3 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) escape-string-regexp: 4.0.0 - eslint-scope: 8.0.2 - eslint-visitor-keys: 4.0.0 - espree: 10.1.0 + eslint-scope: 8.2.0 + eslint-visitor-keys: 4.2.0 + espree: 10.3.0 esquery: 1.6.0 esutils: 2.0.3 fast-deep-equal: 3.1.3 @@ -18171,28 +17672,25 @@ snapshots: ignore: 5.3.1 imurmurhash: 0.1.4 is-glob: 4.0.3 - is-path-inside: 3.0.3 json-stable-stringify-without-jsonify: 1.0.1 - levn: 0.4.1 lodash.merge: 4.6.2 minimatch: 3.1.2 natural-compare: 1.4.0 optionator: 0.9.4 - strip-ansi: 6.0.1 text-table: 0.2.0 transitivePeerDependencies: - supports-color - espree@10.1.0: + espree@10.3.0: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) - eslint-visitor-keys: 4.0.0 + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) + eslint-visitor-keys: 4.2.0 espree@9.6.1: dependencies: - acorn: 8.12.1 - acorn-jsx: 5.3.2(acorn@8.12.1) + acorn: 8.14.0 + acorn-jsx: 5.3.2(acorn@8.14.0) eslint-visitor-keys: 3.4.3 esprima@4.0.1: {} @@ -18301,21 +17799,6 @@ snapshots: signal-exit: 4.1.0 strip-final-newline: 3.0.0 - execa@9.4.0: - dependencies: - '@sindresorhus/merge-streams': 4.0.0 - cross-spawn: 7.0.3 - figures: 6.1.0 - get-stream: 9.0.1 - human-signals: 8.0.0 - is-plain-obj: 4.1.0 - is-stream: 4.0.1 - npm-run-path: 6.0.0 - pretty-ms: 9.0.0 - signal-exit: 4.1.0 - strip-final-newline: 4.0.0 - yoctocolors: 2.0.2 - executable@4.1.1: dependencies: pify: 2.3.0 @@ -18332,34 +17815,34 @@ snapshots: exponential-backoff@3.1.1: {} - express@4.19.2: + express@4.21.0: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 - body-parser: 1.20.2 + body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 cookie: 0.6.0 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 - encodeurl: 1.0.2 + encodeurl: 2.0.0 escape-html: 1.0.3 etag: 1.8.1 - finalhandler: 1.2.0 + finalhandler: 1.3.1 fresh: 0.5.2 http-errors: 2.0.0 - merge-descriptors: 1.0.1 + merge-descriptors: 1.0.3 methods: 1.1.2 on-finished: 2.4.1 parseurl: 1.3.3 - path-to-regexp: 0.1.7 + path-to-regexp: 0.1.10 proxy-addr: 2.0.7 - qs: 6.11.0 + qs: 6.13.0 range-parser: 1.2.1 safe-buffer: 5.2.1 - send: 0.18.0 - serve-static: 1.15.0 + send: 0.19.0 + serve-static: 1.16.2 setprototypeof: 1.2.0 statuses: 2.0.1 type-is: 1.6.18 @@ -18368,14 +17851,14 @@ snapshots: transitivePeerDependencies: - supports-color - express@4.21.0: + express@4.21.1: dependencies: accepts: 1.3.8 array-flatten: 1.1.1 body-parser: 1.20.3 content-disposition: 0.5.4 content-type: 1.0.5 - cookie: 0.6.0 + cookie: 0.7.1 cookie-signature: 1.0.6 debug: 2.6.9 depd: 2.0.0 @@ -18417,7 +17900,7 @@ snapshots: extract-zip@2.0.1(supports-color@8.1.1): dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) get-stream: 5.2.0 yauzl: 2.10.0 optionalDependencies: @@ -18548,10 +18031,6 @@ snapshots: escape-string-regexp: 1.0.5 optional: true - figures@6.1.0: - dependencies: - is-unicode-supported: 2.0.0 - file-entry-cache@6.0.1: dependencies: flat-cache: 3.2.0 @@ -18566,10 +18045,10 @@ snapshots: strtok3: 7.0.0 token-types: 5.0.1 - file-type@19.5.0: + file-type@19.6.0: dependencies: get-stream: 9.0.1 - strtok3: 8.1.0 + strtok3: 9.0.1 token-types: 6.0.0 uint8array-extras: 1.4.0 @@ -18593,18 +18072,6 @@ snapshots: dependencies: to-regex-range: 5.0.1 - finalhandler@1.2.0: - dependencies: - debug: 2.6.9 - encodeurl: 1.0.2 - escape-html: 1.0.3 - on-finished: 2.4.1 - parseurl: 1.3.3 - statuses: 2.0.1 - unpipe: 1.0.0 - transitivePeerDependencies: - - supports-color - finalhandler@1.3.1: dependencies: debug: 2.6.9 @@ -18617,12 +18084,6 @@ snapshots: transitivePeerDependencies: - supports-color - find-cache-dir@3.3.2: - dependencies: - commondir: 1.0.1 - make-dir: 3.1.0 - pkg-dir: 4.2.0 - find-my-way@9.1.0: dependencies: fast-deep-equal: 3.1.3 @@ -18676,7 +18137,7 @@ snapshots: follow-redirects@1.15.9(debug@4.3.7): optionalDependencies: - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) for-each@0.3.3: dependencies: @@ -18700,6 +18161,12 @@ snapshots: combined-stream: 1.0.8 mime-types: 2.1.35 + form-data@4.0.1: + dependencies: + asynckit: 0.4.0 + combined-stream: 1.0.8 + mime-types: 2.1.35 + formdata-polyfill@4.0.10: dependencies: fetch-blob: 3.2.0 @@ -18710,12 +18177,6 @@ snapshots: from@0.1.7: {} - fs-extra@11.1.1: - dependencies: - graceful-fs: 4.2.11 - jsonfile: 6.1.0 - universalify: 2.0.0 - fs-extra@7.0.1: dependencies: graceful-fs: 4.2.11 @@ -18829,8 +18290,6 @@ snapshots: dependencies: assert-plus: 1.0.0 - github-slugger@2.0.0: {} - glob-parent@5.1.2: dependencies: is-glob: 4.0.3 @@ -18891,7 +18350,7 @@ snapshots: globals@14.0.0: {} - globals@15.9.0: {} + globals@15.12.0: {} globalthis@1.0.3: dependencies: @@ -18938,9 +18397,9 @@ snapshots: p-cancelable: 3.0.0 responselike: 3.0.0 - got@14.4.2: + got@14.4.4: dependencies: - '@sindresorhus/is': 7.0.0 + '@sindresorhus/is': 7.0.1 '@szmarczak/http-timer': 5.0.1 cacheable-lookup: 7.0.0 cacheable-request: 12.0.1 @@ -18950,7 +18409,7 @@ snapshots: lowercase-keys: 3.0.0 p-cancelable: 4.0.1 responselike: 3.0.0 - type-fest: 4.20.1 + type-fest: 4.27.0 graceful-fs@4.2.11: {} @@ -18971,7 +18430,7 @@ snapshots: whatwg-encoding: 2.0.0 whatwg-mimetype: 3.0.0 - happy-dom@15.7.4: + happy-dom@15.11.4: dependencies: entities: 4.5.0 webidl-conversions: 7.0.0 @@ -19021,15 +18480,21 @@ snapshots: dependencies: function-bind: 1.1.2 - hast-util-heading-rank@3.0.0: - dependencies: - '@types/hast': 3.0.4 - - hast-util-is-element@3.0.0: + hast-util-to-html@9.0.3: dependencies: '@types/hast': 3.0.4 + '@types/unist': 3.0.2 + ccount: 2.0.1 + comma-separated-tokens: 2.0.3 + hast-util-whitespace: 3.0.0 + html-void-elements: 3.0.0 + mdast-util-to-hast: 13.2.0 + property-information: 6.5.0 + space-separated-tokens: 2.0.2 + stringify-entities: 4.0.4 + zwitch: 2.0.4 - hast-util-to-string@3.0.0: + hast-util-whitespace@3.0.0: dependencies: '@types/hast': 3.0.4 @@ -19057,7 +18522,7 @@ snapshots: html-escaper@2.0.2: {} - html-tags@3.2.0: {} + html-void-elements@3.0.0: {} htmlescape@1.1.1: {} @@ -19097,7 +18562,7 @@ snapshots: http-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -19123,14 +18588,14 @@ snapshots: https-proxy-agent@7.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 transitivePeerDependencies: - supports-color https-proxy-agent@7.0.5: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) transitivePeerDependencies: - supports-color @@ -19143,8 +18608,6 @@ snapshots: human-signals@5.0.0: {} - human-signals@8.0.0: {} - iconv-lite@0.4.24: dependencies: safer-buffer: 2.1.2 @@ -19172,20 +18635,12 @@ snapshots: parent-module: 1.0.1 resolve-from: 4.0.0 - import-in-the-middle@1.10.0: - dependencies: - acorn: 8.12.1 - acorn-import-attributes: 1.9.5(acorn@8.12.1) - cjs-module-lexer: 1.2.2 - module-details-from-path: 1.0.3 - - import-in-the-middle@1.7.1: + import-in-the-middle@1.11.2: dependencies: - acorn: 8.12.1 - acorn-import-assertions: 1.9.0(acorn@8.12.1) + acorn: 8.14.0 + acorn-import-attributes: 1.9.5(acorn@8.14.0) cjs-module-lexer: 1.2.2 module-details-from-path: 1.0.3 - optional: true import-lazy@4.0.0: {} @@ -19234,7 +18689,7 @@ snapshots: dependencies: '@ioredis/commands': 1.2.0 cluster-key-slot: 1.1.2 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 denque: 2.1.0 lodash.defaults: 4.2.0 lodash.isarguments: 3.1.0 @@ -19263,8 +18718,6 @@ snapshots: irregular-plurals@3.5.0: {} - is-absolute-url@4.0.1: {} - is-arguments@1.1.1: dependencies: call-bind: 1.0.2 @@ -19300,11 +18753,6 @@ snapshots: is-callable@1.2.7: {} - is-ci@3.0.1: - dependencies: - ci-info: 3.7.1 - optional: true - is-core-module@2.13.1: dependencies: hasown: 2.0.0 @@ -19338,7 +18786,7 @@ snapshots: is-generator-function@1.0.10: dependencies: - has-tostringtag: 1.0.0 + has-tostringtag: 1.0.2 is-glob@4.0.3: dependencies: @@ -19434,8 +18882,6 @@ snapshots: is-unicode-supported@0.1.0: {} - is-unicode-supported@2.0.0: {} - is-weakmap@2.0.1: {} is-weakref@1.0.2: @@ -19469,7 +18915,7 @@ snapshots: istanbul-lib-instrument@5.2.1: dependencies: '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 semver: 6.3.1 @@ -19479,10 +18925,10 @@ snapshots: istanbul-lib-instrument@6.0.0: dependencies: '@babel/core': 7.24.7 - '@babel/parser': 7.24.7 + '@babel/parser': 7.25.7 '@istanbuljs/schema': 0.1.3 istanbul-lib-coverage: 3.2.2 - semver: 7.6.0 + semver: 7.6.3 transitivePeerDependencies: - supports-color @@ -19494,7 +18940,7 @@ snapshots: istanbul-lib-source-maps@4.0.1: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) istanbul-lib-coverage: 3.2.2 source-map: 0.6.1 transitivePeerDependencies: @@ -19503,7 +18949,7 @@ snapshots: istanbul-lib-source-maps@5.0.4: dependencies: '@jridgewell/trace-mapping': 0.3.25 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) istanbul-lib-coverage: 3.2.2 transitivePeerDependencies: - supports-color @@ -19546,7 +18992,7 @@ snapshots: '@jest/expect': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 chalk: 4.1.2 co: 4.6.0 dedent: 1.3.0 @@ -19566,16 +19012,16 @@ snapshots: - babel-plugin-macros - supports-color - jest-cli@29.7.0(@types/node@20.14.12): + jest-cli@29.7.0(@types/node@22.9.0): dependencies: '@jest/core': 29.7.0 '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 chalk: 4.1.2 - create-jest: 29.7.0(@types/node@20.14.12) + create-jest: 29.7.0(@types/node@22.9.0) exit: 0.1.2 import-local: 3.1.0 - jest-config: 29.7.0(@types/node@20.14.12) + jest-config: 29.7.0(@types/node@22.9.0) jest-util: 29.7.0 jest-validate: 29.7.0 yargs: 17.7.2 @@ -19585,12 +19031,12 @@ snapshots: - supports-color - ts-node - jest-config@29.7.0(@types/node@20.14.12): + jest-config@29.7.0(@types/node@22.9.0): dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.23.5 '@jest/test-sequencer': 29.7.0 '@jest/types': 29.6.3 - babel-jest: 29.7.0(@babel/core@7.24.7) + babel-jest: 29.7.0(@babel/core@7.23.5) chalk: 4.1.2 ci-info: 3.7.1 deepmerge: 4.2.2 @@ -19610,7 +19056,7 @@ snapshots: slash: 3.0.0 strip-json-comments: 3.1.1 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 transitivePeerDependencies: - babel-plugin-macros - supports-color @@ -19639,7 +19085,7 @@ snapshots: '@jest/environment': 29.7.0 '@jest/fake-timers': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 jest-mock: 29.7.0 jest-util: 29.7.0 @@ -19656,7 +19102,7 @@ snapshots: dependencies: '@jest/types': 29.6.3 '@types/graceful-fs': 4.1.6 - '@types/node': 20.14.12 + '@types/node': 22.9.0 anymatch: 3.1.3 fb-watchman: 2.0.2 graceful-fs: 4.2.11 @@ -19682,7 +19128,7 @@ snapshots: jest-message-util@29.7.0: dependencies: - '@babel/code-frame': 7.24.7 + '@babel/code-frame': 7.23.5 '@jest/types': 29.6.3 '@types/stack-utils': 2.0.1 chalk: 4.1.2 @@ -19695,7 +19141,7 @@ snapshots: jest-mock@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 jest-util: 29.7.0 jest-pnp-resolver@1.2.3(jest-resolve@29.7.0): @@ -19730,7 +19176,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 chalk: 4.1.2 emittery: 0.13.1 graceful-fs: 4.2.11 @@ -19758,7 +19204,7 @@ snapshots: '@jest/test-result': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 chalk: 4.1.2 cjs-module-lexer: 1.2.2 collect-v8-coverage: 1.0.1 @@ -19778,15 +19224,15 @@ snapshots: jest-snapshot@29.7.0: dependencies: - '@babel/core': 7.24.7 + '@babel/core': 7.23.5 '@babel/generator': 7.24.7 - '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.24.7) - '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.24.7) + '@babel/plugin-syntax-jsx': 7.23.3(@babel/core@7.23.5) + '@babel/plugin-syntax-typescript': 7.23.3(@babel/core@7.23.5) '@babel/types': 7.24.7 '@jest/expect-utils': 29.7.0 '@jest/transform': 29.7.0 '@jest/types': 29.6.3 - babel-preset-current-node-syntax: 1.0.1(@babel/core@7.24.7) + babel-preset-current-node-syntax: 1.0.1(@babel/core@7.23.5) chalk: 4.1.2 expect: 29.7.0 graceful-fs: 4.2.11 @@ -19797,14 +19243,14 @@ snapshots: jest-util: 29.7.0 natural-compare: 1.4.0 pretty-format: 29.7.0 - semver: 7.6.0 + semver: 7.5.4 transitivePeerDependencies: - supports-color jest-util@29.7.0: dependencies: '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 chalk: 4.1.2 ci-info: 3.7.1 graceful-fs: 4.2.11 @@ -19823,7 +19269,7 @@ snapshots: dependencies: '@jest/test-result': 29.7.0 '@jest/types': 29.6.3 - '@types/node': 20.14.12 + '@types/node': 22.9.0 ansi-escapes: 4.3.2 chalk: 4.1.2 emittery: 0.13.1 @@ -19837,17 +19283,17 @@ snapshots: jest-worker@29.7.0: dependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 jest-util: 29.7.0 merge-stream: 2.0.0 supports-color: 8.1.1 - jest@29.7.0(@types/node@20.14.12): + jest@29.7.0(@types/node@22.9.0): dependencies: '@jest/core': 29.7.0 '@jest/types': 29.6.3 import-local: 3.1.0 - jest-cli: 29.7.0(@types/node@20.14.12) + jest-cli: 29.7.0(@types/node@22.9.0) transitivePeerDependencies: - '@types/node' - babel-plugin-macros @@ -19902,18 +19348,47 @@ snapshots: jsdoc-type-pratt-parser@4.1.0: {} + jsdom@24.1.1: + dependencies: + cssstyle: 4.0.1 + data-urls: 5.0.0 + decimal.js: 10.4.3 + form-data: 4.0.1 + html-encoding-sniffer: 4.0.0 + http-proxy-agent: 7.0.2 + https-proxy-agent: 7.0.5 + is-potential-custom-element-name: 1.0.1 + nwsapi: 2.2.12 + parse5: 7.2.1 + rrweb-cssom: 0.7.1 + saxes: 6.0.0 + symbol-tree: 3.2.4 + tough-cookie: 4.1.4 + w3c-xmlserializer: 5.0.0 + webidl-conversions: 7.0.0 + whatwg-encoding: 3.1.1 + whatwg-mimetype: 4.0.0 + whatwg-url: 14.0.0 + ws: 8.18.0(bufferutil@4.0.7)(utf-8-validate@6.0.3) + xml-name-validator: 5.0.0 + transitivePeerDependencies: + - bufferutil + - supports-color + - utf-8-validate + optional: true + jsdom@24.1.1(bufferutil@4.0.7)(utf-8-validate@6.0.3): dependencies: cssstyle: 4.0.1 data-urls: 5.0.0 decimal.js: 10.4.3 - form-data: 4.0.0 + form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.12 - parse5: 7.1.2 + parse5: 7.2.1 rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -19935,13 +19410,13 @@ snapshots: cssstyle: 4.0.1 data-urls: 5.0.0 decimal.js: 10.4.3 - form-data: 4.0.0 + form-data: 4.0.1 html-encoding-sniffer: 4.0.0 http-proxy-agent: 7.0.2 https-proxy-agent: 7.0.5 is-potential-custom-element-name: 1.0.1 nwsapi: 2.2.12 - parse5: 7.1.2 + parse5: 7.2.1 rrweb-cssom: 0.7.1 saxes: 6.0.0 symbol-tree: 3.2.4 @@ -20007,6 +19482,7 @@ snapshots: universalify: 2.0.0 optionalDependencies: graceful-fs: 4.2.11 + optional: true jsonld@8.3.2(web-streams-polyfill@4.0.0): dependencies: @@ -20113,7 +19589,7 @@ snapshots: colorette: 2.0.19 log-update: 4.0.0 p-map: 4.0.0 - rfdc: 1.3.0 + rfdc: 1.4.1 rxjs: 7.8.1 through: 2.3.8 wrap-ansi: 7.0.0 @@ -20207,7 +19683,7 @@ snapshots: magic-string@0.27.0: dependencies: - '@jridgewell/sourcemap-codec': 1.4.15 + '@jridgewell/sourcemap-codec': 1.5.0 magic-string@0.30.10: dependencies: @@ -20225,10 +19701,6 @@ snapshots: mailcheck@1.1.1: {} - make-dir@3.1.0: - dependencies: - semver: 6.3.1 - make-dir@4.0.0: dependencies: semver: 7.6.0 @@ -20265,10 +19737,6 @@ snapshots: markdown-table@3.0.3: {} - markdown-to-jsx@7.4.7(react@18.3.1): - dependencies: - react: 18.3.1 - marked@4.3.0: {} matter-js@0.19.0: {} @@ -20359,6 +19827,18 @@ snapshots: '@types/mdast': 4.0.3 unist-util-is: 6.0.0 + mdast-util-to-hast@13.2.0: + dependencies: + '@types/hast': 3.0.4 + '@types/mdast': 4.0.3 + '@ungap/structured-clone': 1.2.0 + devlop: 1.1.0 + micromark-util-sanitize-uri: 2.0.0 + trim-lines: 3.0.1 + unist-util-position: 5.0.0 + unist-util-visit: 5.0.0 + vfile: 6.0.1 + mdast-util-to-markdown@2.1.0: dependencies: '@types/mdast': 4.0.3 @@ -20380,11 +19860,7 @@ snapshots: media-typer@0.3.0: {} - meilisearch@0.42.0(encoding@0.1.13): - dependencies: - cross-fetch: 3.1.6(encoding@0.1.13) - transitivePeerDependencies: - - encoding + meilisearch@0.45.0: {} memoizerific@1.11.3: dependencies: @@ -20407,8 +19883,6 @@ snapshots: type-fest: 0.18.1 yargs-parser: 20.2.9 - merge-descriptors@1.0.1: {} - merge-descriptors@1.0.3: {} merge-stream@2.0.0: {} @@ -20419,7 +19893,7 @@ snapshots: microformats-parser@2.0.2: dependencies: - parse5: 7.1.2 + parse5: 7.2.1 micromark-core-commonmark@2.0.0: dependencies: @@ -20593,7 +20067,7 @@ snapshots: micromark@4.0.0: dependencies: '@types/debug': 4.1.12 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) decode-named-character-reference: 1.0.2 devlop: 1.1.0 micromark-core-commonmark: 2.0.0 @@ -20728,7 +20202,7 @@ snapshots: mlly@1.5.0: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 pathe: 1.1.2 pkg-types: 1.0.3 ufo: 1.3.2 @@ -20761,44 +20235,23 @@ snapshots: '@msgpackr-extract/msgpackr-extract-win32-x64': 3.0.2 optional: true - msgpackr@1.10.1: + msgpackr@1.11.2: optionalDependencies: msgpackr-extract: 3.0.2 - msw-storybook-addon@2.0.3(msw@2.4.9(typescript@5.6.2)): + msw-storybook-addon@2.0.4(msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3)): dependencies: is-node-process: 1.2.0 - msw: 2.4.9(typescript@5.6.2) + msw: 2.6.4(@types/node@22.9.0)(typescript@5.6.3) - msw@2.3.4(typescript@5.6.2): + msw@2.6.4(@types/node@22.9.0)(typescript@5.6.3): dependencies: - '@bundled-es-modules/cookie': 2.0.0 + '@bundled-es-modules/cookie': 2.0.1 '@bundled-es-modules/statuses': 1.0.1 '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 3.1.6 - '@mswjs/interceptors': 0.29.1 - '@open-draft/until': 2.1.0 - '@types/cookie': 0.6.0 - '@types/statuses': 2.0.4 - chalk: 4.1.2 - graphql: 16.8.1 - headers-polyfill: 4.0.2 - is-node-process: 1.2.0 - outvariant: 1.4.2 - path-to-regexp: 6.2.1 - strict-event-emitter: 0.5.1 - type-fest: 4.20.1 - yargs: 17.7.2 - optionalDependencies: - typescript: 5.6.2 - - msw@2.4.9(typescript@5.6.2): - dependencies: - '@bundled-es-modules/cookie': 2.0.0 - '@bundled-es-modules/statuses': 1.0.1 - '@bundled-es-modules/tough-cookie': 0.1.6 - '@inquirer/confirm': 3.1.6 - '@mswjs/interceptors': 0.35.9 + '@inquirer/confirm': 5.0.2(@types/node@22.9.0) + '@mswjs/interceptors': 0.36.10 + '@open-draft/deferred-promise': 2.2.0 '@open-draft/until': 2.1.0 '@types/cookie': 0.6.0 '@types/statuses': 2.0.4 @@ -20806,13 +20259,15 @@ snapshots: graphql: 16.8.1 headers-polyfill: 4.0.2 is-node-process: 1.2.0 - outvariant: 1.4.2 + outvariant: 1.4.3 path-to-regexp: 6.3.0 strict-event-emitter: 0.5.1 - type-fest: 4.20.1 + type-fest: 4.27.0 yargs: 17.7.2 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 + transitivePeerDependencies: + - '@types/node' muggle-string@0.4.1: {} @@ -20826,7 +20281,7 @@ snapshots: type-is: 1.6.18 xtend: 4.0.2 - mute-stream@1.0.0: {} + mute-stream@2.0.0: {} mylas@2.1.13: {} @@ -20840,7 +20295,7 @@ snapshots: nanoid@3.3.7: {} - nanoid@5.0.7: {} + nanoid@5.0.8: {} natural-compare@1.4.0: {} @@ -20876,7 +20331,7 @@ snapshots: node-abi@3.62.0: dependencies: - semver: 7.6.0 + semver: 7.6.3 node-abort-controller@3.1.1: {} @@ -20926,20 +20381,7 @@ snapshots: node-releases@2.0.14: {} - nodemailer@6.9.15: {} - - nodemon@3.0.2: - dependencies: - chokidar: 3.5.3 - debug: 4.3.4(supports-color@5.5.0) - ignore-by-default: 1.0.1 - minimatch: 3.1.2 - pstree.remy: 1.1.8 - semver: 7.5.4 - simple-update-notifier: 2.0.0 - supports-color: 5.5.0 - touch: 3.1.0 - undefsafe: 2.0.5 + nodemailer@6.9.16: {} nodemon@3.1.7: dependencies: @@ -21004,11 +20446,6 @@ snapshots: dependencies: path-key: 4.0.0 - npm-run-path@6.0.0: - dependencies: - path-key: 4.0.0 - unicorn-magic: 0.3.0 - nth-check@2.1.1: dependencies: boolbase: 1.0.0 @@ -21099,6 +20536,10 @@ snapshots: dependencies: mimic-fn: 4.0.0 + oniguruma-to-js@0.4.3: + dependencies: + regex: 4.4.0 + open@8.4.2: dependencies: define-lazy-prop: 2.0.0 @@ -21116,15 +20557,6 @@ snapshots: undici: 5.28.2 yargs-parser: 21.1.1 - opentelemetry-instrumentation-fetch-node@1.2.3(@opentelemetry/api@1.9.0): - dependencies: - '@opentelemetry/api': 1.9.0 - '@opentelemetry/instrumentation': 0.46.0(@opentelemetry/api@1.9.0) - '@opentelemetry/semantic-conventions': 1.25.1 - transitivePeerDependencies: - - supports-color - optional: true - optionator@0.9.4: dependencies: deep-is: 0.1.4 @@ -21143,11 +20575,9 @@ snapshots: ospath@1.2.2: optional: true - otpauth@9.3.2: + otpauth@9.3.4: dependencies: - '@noble/hashes': 1.4.0 - - outvariant@1.4.2: {} + '@noble/hashes': 1.5.0 outvariant@1.4.3: {} @@ -21211,8 +20641,6 @@ snapshots: dependencies: xtend: 4.0.2 - parse-ms@4.0.0: {} - parse-srcset@1.0.2: {} parse5-htmlparser2-tree-adapter@6.0.1: @@ -21222,17 +20650,17 @@ snapshots: parse5-htmlparser2-tree-adapter@7.0.0: dependencies: domhandler: 5.0.3 - parse5: 7.1.2 + parse5: 7.2.1 parse5-parser-stream@7.1.2: dependencies: - parse5: 7.1.2 + parse5: 7.2.1 parse5@5.1.1: {} parse5@6.0.1: {} - parse5@7.1.2: + parse5@7.2.1: dependencies: entities: 4.5.0 @@ -21264,16 +20692,12 @@ snapshots: path-to-regexp@0.1.10: {} - path-to-regexp@0.1.7: {} - path-to-regexp@1.8.0: dependencies: isarray: 0.0.1 path-to-regexp@3.3.0: {} - path-to-regexp@6.2.1: {} - path-to-regexp@6.3.0: {} path-type@4.0.0: {} @@ -21288,10 +20712,10 @@ snapshots: dependencies: through: 2.3.8 - peek-readable@5.1.3: {} - peek-readable@5.2.0: {} + peek-readable@5.3.1: {} + pend@1.2.0: optional: true @@ -21307,9 +20731,9 @@ snapshots: pg-numeric@1.0.2: {} - pg-pool@3.7.0(pg@8.13.0): + pg-pool@3.7.0(pg@8.13.1): dependencies: - pg: 8.13.0 + pg: 8.13.1 pg-protocol@1.6.1: {} @@ -21333,10 +20757,10 @@ snapshots: postgres-interval: 3.0.0 postgres-range: 1.1.3 - pg@8.13.0: + pg@8.13.1: dependencies: pg-connection-string: 2.7.0 - pg-pool: 3.7.0(pg@8.13.0) + pg-pool: 3.7.0(pg@8.13.1) pg-protocol: 1.7.0 pg-types: 2.2.0 pgpass: 1.0.5 @@ -21355,8 +20779,12 @@ snapshots: picocolors@1.1.0: {} + picocolors@1.1.1: {} + picomatch@2.3.1: {} + picomatch@4.0.2: {} + pid-port@1.0.0: dependencies: execa: 8.0.1 @@ -21418,140 +20846,140 @@ snapshots: possible-typed-array-names@1.0.0: {} - postcss-calc@9.0.1(postcss@8.4.47): + postcss-calc@9.0.1(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-selector-parser: 6.0.15 postcss-value-parser: 4.2.0 - postcss-colormin@6.1.0(postcss@8.4.47): + postcss-colormin@6.1.0(postcss@8.4.49): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 colord: 2.9.3 - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-convert-values@6.1.0(postcss@8.4.47): + postcss-convert-values@6.1.0(postcss@8.4.49): dependencies: browserslist: 4.23.0 - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-discard-comments@6.0.2(postcss@8.4.47): + postcss-discard-comments@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-discard-duplicates@6.0.3(postcss@8.4.47): + postcss-discard-duplicates@6.0.3(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-discard-empty@6.0.3(postcss@8.4.47): + postcss-discard-empty@6.0.3(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-discard-overridden@6.0.2(postcss@8.4.47): + postcss-discard-overridden@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-merge-longhand@6.0.5(postcss@8.4.47): + postcss-merge-longhand@6.0.5(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - stylehacks: 6.1.1(postcss@8.4.47) + stylehacks: 6.1.1(postcss@8.4.49) - postcss-merge-rules@6.1.1(postcss@8.4.47): + postcss-merge-rules@6.1.1(postcss@8.4.49): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 - cssnano-utils: 4.0.2(postcss@8.4.47) - postcss: 8.4.47 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-selector-parser: 6.0.16 - postcss-minify-font-values@6.1.0(postcss@8.4.47): + postcss-minify-font-values@6.1.0(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-gradients@6.0.3(postcss@8.4.47): + postcss-minify-gradients@6.0.3(postcss@8.4.49): dependencies: colord: 2.9.3 - cssnano-utils: 4.0.2(postcss@8.4.47) - postcss: 8.4.47 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-params@6.1.0(postcss@8.4.47): + postcss-minify-params@6.1.0(postcss@8.4.49): dependencies: browserslist: 4.23.0 - cssnano-utils: 4.0.2(postcss@8.4.47) - postcss: 8.4.47 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-minify-selectors@6.0.4(postcss@8.4.47): + postcss-minify-selectors@6.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-selector-parser: 6.0.16 - postcss-normalize-charset@6.0.2(postcss@8.4.47): + postcss-normalize-charset@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 - postcss-normalize-display-values@6.0.2(postcss@8.4.47): + postcss-normalize-display-values@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-positions@6.0.2(postcss@8.4.47): + postcss-normalize-positions@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-repeat-style@6.0.2(postcss@8.4.47): + postcss-normalize-repeat-style@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-string@6.0.2(postcss@8.4.47): + postcss-normalize-string@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-timing-functions@6.0.2(postcss@8.4.47): + postcss-normalize-timing-functions@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-unicode@6.1.0(postcss@8.4.47): + postcss-normalize-unicode@6.1.0(postcss@8.4.49): dependencies: browserslist: 4.23.0 - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-url@6.0.2(postcss@8.4.47): + postcss-normalize-url@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-normalize-whitespace@6.0.2(postcss@8.4.47): + postcss-normalize-whitespace@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-ordered-values@6.0.2(postcss@8.4.47): + postcss-ordered-values@6.0.2(postcss@8.4.49): dependencies: - cssnano-utils: 4.0.2(postcss@8.4.47) - postcss: 8.4.47 + cssnano-utils: 4.0.2(postcss@8.4.49) + postcss: 8.4.49 postcss-value-parser: 4.2.0 - postcss-reduce-initial@6.1.0(postcss@8.4.47): + postcss-reduce-initial@6.1.0(postcss@8.4.49): dependencies: browserslist: 4.23.0 caniuse-api: 3.0.0 - postcss: 8.4.47 + postcss: 8.4.49 - postcss-reduce-transforms@6.0.2(postcss@8.4.47): + postcss-reduce-transforms@6.0.2(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 postcss-selector-parser@6.0.15: @@ -21564,29 +20992,29 @@ snapshots: cssesc: 3.0.0 util-deprecate: 1.0.2 - postcss-svgo@6.0.3(postcss@8.4.47): + postcss-svgo@6.0.3(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-value-parser: 4.2.0 svgo: 3.2.0 - postcss-unique-selectors@6.0.4(postcss@8.4.47): + postcss-unique-selectors@6.0.4(postcss@8.4.49): dependencies: - postcss: 8.4.47 + postcss: 8.4.49 postcss-selector-parser: 6.0.16 postcss-value-parser@4.2.0: {} - postcss@8.4.38: + postcss@8.4.47: dependencies: nanoid: 3.3.7 - picocolors: 1.0.0 - source-map-js: 1.2.0 + picocolors: 1.1.0 + source-map-js: 1.2.1 - postcss@8.4.47: + postcss@8.4.49: dependencies: nanoid: 3.3.7 - picocolors: 1.1.0 + picocolors: 1.1.1 source-map-js: 1.2.1 postgres-array@2.0.0: {} @@ -21630,10 +21058,6 @@ snapshots: ansi-styles: 5.2.0 react-is: 18.2.0 - pretty-ms@9.0.0: - dependencies: - parse-ms: 4.0.0 - private-ip@2.3.3: dependencies: ip-regex: 4.3.0 @@ -21681,11 +21105,7 @@ snapshots: kleur: 3.0.3 sisteransi: 1.0.5 - prop-types@15.8.1: - dependencies: - loose-envify: 1.4.0 - object-assign: 4.1.1 - react-is: 16.13.1 + property-information@6.5.0: {} proto-list@1.2.4: {} @@ -21793,7 +21213,7 @@ snapshots: pvtsutils@1.3.5: dependencies: - tslib: 2.6.3 + tslib: 2.7.0 pvutils@1.1.3: {} @@ -21803,10 +21223,6 @@ snapshots: pngjs: 5.0.0 yargs: 15.4.1 - qs@6.11.0: - dependencies: - side-channel: 1.0.4 - qs@6.13.0: dependencies: side-channel: 1.0.6 @@ -21859,20 +21275,15 @@ snapshots: transitivePeerDependencies: - supports-color - react-colorful@5.6.1(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - - react-docgen-typescript@2.2.2(typescript@5.6.2): + react-docgen-typescript@2.2.2(typescript@5.6.3): dependencies: - typescript: 5.6.2 + typescript: 5.6.3 react-docgen@7.0.1: dependencies: '@babel/core': 7.24.7 '@babel/traverse': 7.24.7 - '@babel/types': 7.24.7 + '@babel/types': 7.25.7 '@types/babel__core': 7.20.0 '@types/babel__traverse': 7.20.0 '@types/doctrine': 0.0.9 @@ -21889,20 +21300,8 @@ snapshots: react: 18.3.1 scheduler: 0.23.2 - react-element-to-jsx-string@15.0.0(react-dom@18.3.1(react@18.3.1))(react@18.3.1): - dependencies: - '@base2/pretty-print-object': 1.0.1 - is-plain-object: 5.0.0 - react: 18.3.1 - react-dom: 18.3.1(react@18.3.1) - react-is: 18.1.0 - - react-is@16.13.1: {} - react-is@17.0.2: {} - react-is@18.1.0: {} - react-is@18.2.0: {} react@18.3.1: @@ -21965,7 +21364,7 @@ snapshots: esprima: 4.0.1 source-map: 0.6.1 tiny-invariant: 1.3.3 - tslib: 2.6.3 + tslib: 2.7.0 reconnecting-websocket@4.4.0: {} @@ -21990,6 +21389,8 @@ snapshots: regenerator-runtime@0.14.0: {} + regex@4.4.0: {} + regexp.prototype.flags@1.5.0: dependencies: call-bind: 1.0.2 @@ -22003,23 +21404,6 @@ snapshots: es-errors: 1.3.0 set-function-name: 2.0.2 - rehype-external-links@3.0.0: - dependencies: - '@types/hast': 3.0.4 - '@ungap/structured-clone': 1.2.0 - hast-util-is-element: 3.0.0 - is-absolute-url: 4.0.1 - space-separated-tokens: 2.0.2 - unist-util-visit: 5.0.0 - - rehype-slug@6.0.0: - dependencies: - '@types/hast': 3.0.4 - github-slugger: 2.0.0 - hast-util-heading-rank: 3.0.0 - hast-util-to-string: 3.0.0 - unist-util-visit: 5.0.0 - remark-gfm@4.0.0: dependencies: '@types/mdast': 4.0.3 @@ -22063,7 +21447,7 @@ snapshots: require-in-the-middle@7.3.0: dependencies: - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) module-details-from-path: 1.0.3 resolve: 1.22.8 transitivePeerDependencies: @@ -22113,35 +21497,34 @@ snapshots: reusify@1.0.4: {} - rfdc@1.3.0: - optional: true - rfdc@1.4.1: {} rimraf@3.0.2: dependencies: glob: 7.2.3 - rollup@4.22.5: + rollup@4.26.0: dependencies: '@types/estree': 1.0.6 optionalDependencies: - '@rollup/rollup-android-arm-eabi': 4.22.5 - '@rollup/rollup-android-arm64': 4.22.5 - '@rollup/rollup-darwin-arm64': 4.22.5 - '@rollup/rollup-darwin-x64': 4.22.5 - '@rollup/rollup-linux-arm-gnueabihf': 4.22.5 - '@rollup/rollup-linux-arm-musleabihf': 4.22.5 - '@rollup/rollup-linux-arm64-gnu': 4.22.5 - '@rollup/rollup-linux-arm64-musl': 4.22.5 - '@rollup/rollup-linux-powerpc64le-gnu': 4.22.5 - '@rollup/rollup-linux-riscv64-gnu': 4.22.5 - '@rollup/rollup-linux-s390x-gnu': 4.22.5 - '@rollup/rollup-linux-x64-gnu': 4.22.5 - '@rollup/rollup-linux-x64-musl': 4.22.5 - '@rollup/rollup-win32-arm64-msvc': 4.22.5 - '@rollup/rollup-win32-ia32-msvc': 4.22.5 - '@rollup/rollup-win32-x64-msvc': 4.22.5 + '@rollup/rollup-android-arm-eabi': 4.26.0 + '@rollup/rollup-android-arm64': 4.26.0 + '@rollup/rollup-darwin-arm64': 4.26.0 + '@rollup/rollup-darwin-x64': 4.26.0 + '@rollup/rollup-freebsd-arm64': 4.26.0 + '@rollup/rollup-freebsd-x64': 4.26.0 + '@rollup/rollup-linux-arm-gnueabihf': 4.26.0 + '@rollup/rollup-linux-arm-musleabihf': 4.26.0 + '@rollup/rollup-linux-arm64-gnu': 4.26.0 + '@rollup/rollup-linux-arm64-musl': 4.26.0 + '@rollup/rollup-linux-powerpc64le-gnu': 4.26.0 + '@rollup/rollup-linux-riscv64-gnu': 4.26.0 + '@rollup/rollup-linux-s390x-gnu': 4.26.0 + '@rollup/rollup-linux-x64-gnu': 4.26.0 + '@rollup/rollup-linux-x64-musl': 4.26.0 + '@rollup/rollup-win32-arm64-msvc': 4.26.0 + '@rollup/rollup-win32-ia32-msvc': 4.26.0 + '@rollup/rollup-win32-x64-msvc': 4.26.0 fsevents: 2.3.3 rrweb-cssom@0.6.0: {} @@ -22199,14 +21582,14 @@ snapshots: safer-buffer@2.1.2: {} - sanitize-html@2.13.0: + sanitize-html@2.13.1: dependencies: deepmerge: 4.2.2 escape-string-regexp: 4.0.0 htmlparser2: 8.0.1 is-plain-object: 5.0.0 parse-srcset: 1.0.2 - postcss: 8.4.38 + postcss: 8.4.47 sass@1.79.3: dependencies: @@ -22214,6 +21597,12 @@ snapshots: immutable: 4.2.2 source-map-js: 1.2.0 + sass@1.79.4: + dependencies: + chokidar: 3.5.3 + immutable: 4.2.2 + source-map-js: 1.2.1 + sax@1.2.4: {} saxes@6.0.0: @@ -22250,24 +21639,6 @@ snapshots: semver@7.6.3: {} - send@0.18.0: - dependencies: - debug: 2.6.9 - depd: 2.0.0 - destroy: 1.2.0 - encodeurl: 1.0.2 - escape-html: 1.0.3 - etag: 1.8.1 - fresh: 0.5.2 - http-errors: 2.0.0 - mime: 1.6.0 - ms: 2.1.3 - on-finished: 2.4.1 - range-parser: 1.2.1 - statuses: 2.0.1 - transitivePeerDependencies: - - supports-color - send@0.19.0: dependencies: debug: 2.6.9 @@ -22286,15 +21657,6 @@ snapshots: transitivePeerDependencies: - supports-color - serve-static@1.15.0: - dependencies: - encodeurl: 1.0.2 - escape-html: 1.0.3 - parseurl: 1.3.3 - send: 0.18.0 - transitivePeerDependencies: - - supports-color - serve-static@1.16.2: dependencies: encodeurl: 2.0.0 @@ -22378,9 +21740,13 @@ snapshots: vscode-oniguruma: 1.7.0 vscode-textmate: 8.0.0 - shiki@1.12.0: + shiki@1.22.2: dependencies: - '@shikijs/core': 1.12.0 + '@shikijs/core': 1.22.2 + '@shikijs/engine-javascript': 1.22.2 + '@shikijs/engine-oniguruma': 1.22.2 + '@shikijs/types': 1.22.2 + '@shikijs/vscode-textmate': 9.3.0 '@types/hast': 3.0.4 shimmer@1.2.1: {} @@ -22408,7 +21774,7 @@ snapshots: dependencies: '@hapi/hoek': 11.0.4 '@hapi/wreck': 18.0.1 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) joi: 17.11.0 transitivePeerDependencies: - supports-color @@ -22510,7 +21876,7 @@ snapshots: socks-proxy-agent@8.0.2: dependencies: agent-base: 7.1.0 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.5(supports-color@5.5.0) socks: 2.7.1 transitivePeerDependencies: - supports-color @@ -22620,7 +21986,7 @@ snapshots: arg: 5.0.2 bluebird: 3.7.2 check-more-types: 2.24.0 - debug: 4.3.7 + debug: 4.3.7(supports-color@8.1.1) execa: 5.1.1 lazy-ass: 1.6.0 ps-tree: 1.2.0 @@ -22636,22 +22002,24 @@ snapshots: dependencies: internal-slot: 1.0.5 - storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/components@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/core-events@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/theming@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(@storybook/types@8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): + storybook-addon-misskey-theme@https://codeload.github.com/misskey-dev/storybook-addon-misskey-theme/tar.gz/cf583db098365b2ccc81a82f63ca9c93bc32b640(@storybook/blocks@8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/components@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/core-events@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/manager-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/preview-api@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/theming@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(@storybook/types@8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)))(react-dom@18.3.1(react@18.3.1))(react@18.3.1): dependencies: - '@storybook/blocks': 8.3.3(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/components': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/core-events': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/manager-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/preview-api': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/theming': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) - '@storybook/types': 8.3.3(storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4)) + '@storybook/blocks': 8.4.4(react-dom@18.3.1(react@18.3.1))(react@18.3.1)(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/components': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/core-events': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/manager-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/preview-api': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/theming': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) + '@storybook/types': 8.4.4(storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4)) optionalDependencies: react: 18.3.1 react-dom: 18.3.1(react@18.3.1) - storybook@8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4): + storybook@8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4): dependencies: - '@storybook/core': 8.3.3(bufferutil@4.0.8)(utf-8-validate@6.0.4) + '@storybook/core': 8.4.4(bufferutil@4.0.8)(prettier@3.3.3)(utf-8-validate@6.0.4) + optionalDependencies: + prettier: 3.3.3 transitivePeerDependencies: - bufferutil - supports-color @@ -22747,6 +22115,11 @@ snapshots: dependencies: safe-buffer: 5.2.1 + stringify-entities@4.0.4: + dependencies: + character-entities-html4: 2.1.0 + character-entities-legacy: 3.0.0 + stringz@2.1.0: dependencies: char-regex: 1.0.2 @@ -22769,8 +22142,6 @@ snapshots: strip-final-newline@3.0.0: {} - strip-final-newline@4.0.0: {} - strip-indent@3.0.0: dependencies: min-indent: 1.0.1 @@ -22792,17 +22163,17 @@ snapshots: strtok3@7.0.0: dependencies: '@tokenizer/token': 0.3.0 - peek-readable: 5.1.3 + peek-readable: 5.2.0 - strtok3@8.1.0: + strtok3@9.0.1: dependencies: '@tokenizer/token': 0.3.0 - peek-readable: 5.2.0 + peek-readable: 5.3.1 - stylehacks@6.1.1(postcss@8.4.47): + stylehacks@6.1.1(postcss@8.4.49): dependencies: browserslist: 4.23.0 - postcss: 8.4.47 + postcss: 8.4.49 postcss-selector-parser: 6.0.16 supports-color@5.5.0: @@ -22859,14 +22230,10 @@ snapshots: dependencies: execa: 6.1.0 - telejson@7.2.0: - dependencies: - memoizerific: 1.11.3 - - terser@5.33.0: + terser@5.36.0: dependencies: '@jridgewell/source-map': 0.3.6 - acorn: 8.12.1 + acorn: 8.14.0 commander: 2.20.3 source-map-support: 0.5.21 @@ -22917,6 +22284,14 @@ snapshots: tinyspy@3.0.2: {} + tldts-core@6.1.63: + optional: true + + tldts@6.1.63: + dependencies: + tldts-core: 6.1.63 + optional: true + tmp@0.2.3: {} tmpl@1.0.5: {} @@ -22956,6 +22331,11 @@ snapshots: universalify: 0.2.0 url-parse: 1.5.10 + tough-cookie@5.0.0: + dependencies: + tldts: 6.1.63 + optional: true + tr46@0.0.3: {} tr46@5.0.0: @@ -22964,6 +22344,11 @@ snapshots: trace-redirect@1.0.6: {} + tree-kill@1.2.2: + optional: true + + trim-lines@3.0.1: {} + trim-newlines@3.0.1: {} trim-repeated@2.0.0: @@ -22976,27 +22361,19 @@ snapshots: dependencies: typescript: 5.1.6 - ts-api-utils@1.3.0(typescript@5.3.3): - dependencies: - typescript: 5.3.3 - - ts-api-utils@1.3.0(typescript@5.5.4): + ts-api-utils@1.3.0(typescript@5.6.3): dependencies: - typescript: 5.5.4 + typescript: 5.6.3 - ts-api-utils@1.3.0(typescript@5.6.2): - dependencies: - typescript: 5.6.2 - - ts-case-convert@2.0.7: {} + ts-case-convert@2.1.0: {} ts-dedent@2.2.0: {} - ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.23.1)(jest@29.7.0(@types/node@20.14.12))(typescript@5.1.6): + ts-jest@29.1.2(@babel/core@7.24.7)(@jest/types@29.6.3)(babel-jest@29.7.0(@babel/core@7.24.7))(esbuild@0.24.0)(jest@29.7.0(@types/node@22.9.0))(typescript@5.1.6): dependencies: bs-logger: 0.2.6 fast-json-stable-stringify: 2.1.0 - jest: 29.7.0(@types/node@20.14.12) + jest: 29.7.0(@types/node@22.9.0) jest-util: 29.7.0 json5: 2.2.3 lodash.memoize: 4.1.2 @@ -23008,7 +22385,7 @@ snapshots: '@babel/core': 7.24.7 '@jest/types': 29.6.3 babel-jest: 29.7.0(@babel/core@7.24.7) - esbuild: 0.23.1 + esbuild: 0.24.0 ts-map@1.0.3: {} @@ -23082,7 +22459,7 @@ snapshots: type-fest@2.19.0: {} - type-fest@4.20.1: {} + type-fest@4.27.0: {} type-is@1.6.18: dependencies: @@ -23158,7 +22535,7 @@ snapshots: shiki: 0.14.7 typescript: 5.1.6 - typeorm@0.3.20(ioredis@5.4.1)(pg@8.13.0): + typeorm@0.3.20(ioredis@5.4.1)(pg@8.13.1): dependencies: '@sqltools/formatter': 1.2.5 app-root-path: 3.1.0 @@ -23166,7 +22543,7 @@ snapshots: chalk: 4.1.2 cli-highlight: 2.1.11 dayjs: 1.11.10 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 dotenv: 16.0.3 glob: 10.3.10 mkdirp: 2.1.6 @@ -23177,19 +22554,15 @@ snapshots: yargs: 17.7.2 optionalDependencies: ioredis: 5.4.1 - pg: 8.13.0 + pg: 8.13.1 transitivePeerDependencies: - supports-color typescript@5.1.6: {} - typescript@5.3.3: {} - typescript@5.4.2: {} - typescript@5.5.4: {} - - typescript@5.6.2: {} + typescript@5.6.3: {} ufo@1.3.2: {} @@ -23212,8 +22585,6 @@ snapshots: undefsafe@2.0.5: {} - undici-types@5.26.5: {} - undici-types@6.19.8: {} undici@5.28.2: @@ -23222,8 +22593,6 @@ snapshots: undici@6.20.0: {} - unicorn-magic@0.3.0: {} - unified@11.0.4: dependencies: '@types/unist': 3.0.2 @@ -23246,6 +22615,10 @@ snapshots: dependencies: '@types/unist': 3.0.2 + unist-util-position@5.0.0: + dependencies: + '@types/unist': 3.0.2 + unist-util-stringify-position@4.0.0: dependencies: '@types/unist': 3.0.2 @@ -23265,7 +22638,8 @@ snapshots: universalify@0.2.0: {} - universalify@2.0.0: {} + universalify@2.0.0: + optional: true unload@2.4.1: {} @@ -23273,7 +22647,7 @@ snapshots: unplugin@1.4.0: dependencies: - acorn: 8.12.1 + acorn: 8.14.0 chokidar: 3.5.3 webpack-sources: 3.2.3 webpack-virtual-modules: 0.5.0 @@ -23281,6 +22655,12 @@ snapshots: untildify@4.0.0: optional: true + update-browserslist-db@1.0.13(browserslist@4.22.2): + dependencies: + browserslist: 4.22.2 + escalade: 3.1.1 + picocolors: 1.0.0 + update-browserslist-db@1.0.13(browserslist@4.23.0): dependencies: browserslist: 4.23.0 @@ -23313,8 +22693,8 @@ snapshots: inherits: 2.0.4 is-arguments: 1.1.1 is-generator-function: 1.0.10 - is-typed-array: 1.1.10 - which-typed-array: 1.1.11 + is-typed-array: 1.1.13 + which-typed-array: 1.1.15 utils-merge@1.0.1: {} @@ -23325,13 +22705,13 @@ snapshots: uuid@9.0.1: {} - v-code-diff@1.13.1(vue@3.5.10(typescript@5.6.2)): + v-code-diff@1.13.1(vue@3.5.12(typescript@5.6.3)): dependencies: diff: 5.2.0 diff-match-patch: 1.0.5 highlight.js: 11.10.0 - vue: 3.5.10(typescript@5.6.2) - vue-demi: 0.14.7(vue@3.5.10(typescript@5.6.2)) + vue: 3.5.12(typescript@5.6.3) + vue-demi: 0.14.7(vue@3.5.12(typescript@5.6.3)) v8-to-istanbul@9.2.0: dependencies: @@ -23365,13 +22745,31 @@ snapshots: unist-util-stringify-position: 4.0.0 vfile-message: 4.0.2 - vite-node@1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0): + vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0): dependencies: cac: 6.7.14 - debug: 4.3.5(supports-color@8.1.1) + debug: 4.3.7(supports-color@8.1.1) pathe: 1.1.2 picocolors: 1.0.1 - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) + transitivePeerDependencies: + - '@types/node' + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + + vite-node@1.6.0(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0): + dependencies: + cac: 6.7.14 + debug: 4.3.7(supports-color@8.1.1) + pathe: 1.1.2 + picocolors: 1.0.1 + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) transitivePeerDependencies: - '@types/node' - less @@ -23385,25 +22783,36 @@ snapshots: vite-plugin-turbosnap@1.0.3: {} - vite@5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0): + vite@5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0): dependencies: esbuild: 0.21.5 - postcss: 8.4.47 - rollup: 4.22.5 + postcss: 8.4.49 + rollup: 4.26.0 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 fsevents: 2.3.3 sass: 1.79.3 - terser: 5.33.0 + terser: 5.36.0 - vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0)): + vite@5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0): + dependencies: + esbuild: 0.21.5 + postcss: 8.4.49 + rollup: 4.26.0 + optionalDependencies: + '@types/node': 22.9.0 + fsevents: 2.3.3 + sass: 1.79.4 + terser: 5.36.0 + + vitest-fetch-mock@0.2.2(encoding@0.1.13)(vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0)): dependencies: cross-fetch: 3.1.6(encoding@0.1.13) - vitest: 1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0) + vitest: 1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0) transitivePeerDependencies: - encoding - vitest@1.6.0(@types/node@20.14.12)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.33.0): + vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4))(sass@1.79.3)(terser@5.36.0): dependencies: '@vitest/expect': 1.6.0 '@vitest/runner': 1.6.0 @@ -23412,7 +22821,7 @@ snapshots: '@vitest/utils': 1.6.0 acorn-walk: 8.3.2 chai: 4.3.10 - debug: 4.3.4(supports-color@5.5.0) + debug: 4.3.4 execa: 8.0.1 local-pkg: 0.5.0 magic-string: 0.30.10 @@ -23422,11 +22831,11 @@ snapshots: strip-literal: 2.1.0 tinybench: 2.6.0 tinypool: 0.8.4 - vite: 5.4.8(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) - vite-node: 1.6.0(@types/node@20.14.12)(sass@1.79.3)(terser@5.33.0) + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) + vite-node: 1.6.0(@types/node@22.9.0)(sass@1.79.3)(terser@5.36.0) why-is-node-running: 2.2.2 optionalDependencies: - '@types/node': 20.14.12 + '@types/node': 22.9.0 happy-dom: 10.0.3 jsdom: 24.1.1(bufferutil@4.0.8)(utf-8-validate@6.0.4) transitivePeerDependencies: @@ -23439,6 +22848,42 @@ snapshots: - supports-color - terser + vitest@1.6.0(@types/node@22.9.0)(happy-dom@10.0.3)(jsdom@24.1.1)(sass@1.79.4)(terser@5.36.0): + dependencies: + '@vitest/expect': 1.6.0 + '@vitest/runner': 1.6.0 + '@vitest/snapshot': 1.6.0 + '@vitest/spy': 1.6.0 + '@vitest/utils': 1.6.0 + acorn-walk: 8.3.2 + chai: 4.3.10 + debug: 4.3.4 + execa: 8.0.1 + local-pkg: 0.5.0 + magic-string: 0.30.10 + pathe: 1.1.2 + picocolors: 1.0.0 + std-env: 3.7.0 + strip-literal: 2.1.0 + tinybench: 2.6.0 + tinypool: 0.8.4 + vite: 5.4.11(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) + vite-node: 1.6.0(@types/node@22.9.0)(sass@1.79.4)(terser@5.36.0) + why-is-node-running: 2.2.2 + optionalDependencies: + '@types/node': 22.9.0 + happy-dom: 10.0.3 + jsdom: 24.1.1 + transitivePeerDependencies: + - less + - lightningcss + - sass + - sass-embedded + - stylus + - sugarss + - supports-color + - terser + void-elements@3.1.0: {} vscode-jsonrpc@8.2.0: {} @@ -23446,7 +22891,7 @@ snapshots: vscode-languageclient@9.0.1: dependencies: minimatch: 5.1.2 - semver: 7.6.0 + semver: 7.6.3 vscode-languageserver-protocol: 3.17.5 vscode-languageserver-protocol@3.17.5: @@ -23468,14 +22913,14 @@ snapshots: vscode-uri@3.0.8: {} - vue-component-meta@2.0.16(typescript@5.6.2): + vue-component-meta@2.0.16(typescript@5.6.3): dependencies: '@volar/typescript': 2.2.0 - '@vue/language-core': 2.0.16(typescript@5.6.2) + '@vue/language-core': 2.0.16(typescript@5.6.3) path-browserify: 1.0.1 vue-component-type-helpers: 2.0.16 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 vue-component-type-helpers@1.8.4: {} @@ -23483,31 +22928,29 @@ snapshots: vue-component-type-helpers@2.1.10: {} - vue-component-type-helpers@2.1.6: {} - - vue-demi@0.14.7(vue@3.5.10(typescript@5.6.2)): + vue-demi@0.14.7(vue@3.5.12(typescript@5.6.3)): dependencies: - vue: 3.5.10(typescript@5.6.2) + vue: 3.5.12(typescript@5.6.3) - vue-docgen-api@4.75.1(vue@3.5.10(typescript@5.6.2)): + vue-docgen-api@4.75.1(vue@3.5.12(typescript@5.6.3)): dependencies: - '@babel/parser': 7.24.7 - '@babel/types': 7.24.7 - '@vue/compiler-dom': 3.4.37 - '@vue/compiler-sfc': 3.5.10 + '@babel/parser': 7.25.7 + '@babel/types': 7.25.7 + '@vue/compiler-dom': 3.5.11 + '@vue/compiler-sfc': 3.5.12 ast-types: 0.16.1 hash-sum: 2.0.0 lru-cache: 8.0.4 pug: 3.0.3 recast: 0.23.6 ts-map: 1.0.3 - vue: 3.5.10(typescript@5.6.2) - vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.10(typescript@5.6.2)) + vue: 3.5.12(typescript@5.6.3) + vue-inbrowser-compiler-independent-utils: 4.71.1(vue@3.5.12(typescript@5.6.3)) - vue-eslint-parser@9.4.3(eslint@9.8.0): + vue-eslint-parser@9.4.3(eslint@9.14.0): dependencies: - debug: 4.3.4(supports-color@5.5.0) - eslint: 9.8.0 + debug: 4.3.4 + eslint: 9.14.0 eslint-scope: 7.2.2 eslint-visitor-keys: 3.4.3 espree: 9.6.1 @@ -23517,46 +22960,36 @@ snapshots: transitivePeerDependencies: - supports-color - vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.10(typescript@5.6.2)): + vue-inbrowser-compiler-independent-utils@4.71.1(vue@3.5.12(typescript@5.6.3)): dependencies: - vue: 3.5.10(typescript@5.6.2) + vue: 3.5.12(typescript@5.6.3) vue-template-compiler@2.7.14: dependencies: de-indent: 1.0.2 he: 1.2.0 - vue-tsc@2.1.6(typescript@5.6.2): + vue-tsc@2.1.10(typescript@5.6.3): dependencies: - '@volar/typescript': 2.4.6 - '@vue/language-core': 2.1.6(typescript@5.6.2) - semver: 7.6.0 - typescript: 5.6.2 - - vue@3.4.37(typescript@5.5.4): - dependencies: - '@vue/compiler-dom': 3.4.37 - '@vue/compiler-sfc': 3.4.37 - '@vue/runtime-dom': 3.4.37 - '@vue/server-renderer': 3.4.37(vue@3.4.37(typescript@5.5.4)) - '@vue/shared': 3.4.37 - optionalDependencies: - typescript: 5.5.4 + '@volar/typescript': 2.4.10 + '@vue/language-core': 2.1.10(typescript@5.6.3) + semver: 7.6.3 + typescript: 5.6.3 - vue@3.5.10(typescript@5.6.2): + vue@3.5.12(typescript@5.6.3): dependencies: - '@vue/compiler-dom': 3.5.10 - '@vue/compiler-sfc': 3.5.10 - '@vue/runtime-dom': 3.5.10 - '@vue/server-renderer': 3.5.10(vue@3.5.10(typescript@5.6.2)) - '@vue/shared': 3.5.10 + '@vue/compiler-dom': 3.5.12 + '@vue/compiler-sfc': 3.5.12 + '@vue/runtime-dom': 3.5.12 + '@vue/server-renderer': 3.5.12(vue@3.5.12(typescript@5.6.3)) + '@vue/shared': 3.5.12 optionalDependencies: - typescript: 5.6.2 + typescript: 5.6.3 - vuedraggable@4.1.0(vue@3.5.10(typescript@5.6.2)): + vuedraggable@4.1.0(vue@3.5.12(typescript@5.6.3)): dependencies: sortablejs: 1.14.0 - vue: 3.5.10(typescript@5.6.2) + vue: 3.5.12(typescript@5.6.3) w3c-xmlserializer@5.0.0: dependencies: @@ -23812,7 +23245,7 @@ snapshots: yocto-queue@1.0.0: {} - yoctocolors@2.0.2: {} + yoctocolors-cjs@2.1.2: {} zip-stream@6.0.1: dependencies: |